Wikilivres frwikibooks https://fr.wikibooks.org/wiki/Accueil MediaWiki 1.46.0-wmf.26 first-letter Média Spécial Discussion Utilisateur Discussion utilisateur Wikilivres Discussion Wikilivres Fichier Discussion fichier MediaWiki Discussion MediaWiki Modèle Discussion modèle Aide Discussion aide Catégorie Discussion catégorie Transwiki Discussion Transwiki Wikijunior Discussion Wikijunior TimedText TimedText talk Module Discussion module Event Event talk Photographie/Composition/Comment rendre l'impression de relief 0 20650 766034 742477 2026-05-05T10:57:41Z -wuppertaler 121494 ([[c:GR|GR]]) [[c:COM:FR|File renamed]]: [[File:Bridge and cables.jpg]] → [[File:Sundial Bridge and cables (35034359904).jpg]] [[c:COM:FR#FR2|Criterion 2]] 766034 wikitext text/x-wiki {{Ph s Composition}} == Généralités == Nous vivons dans un monde à trois dimensions dont la peinture ou la photographie « ordinaires » ne peuvent donner que des représentations à deux dimensions, d'où un évident problème à résoudre. == Utilisez les points de fuite à bon escient == Les façons de jouer avec les points de fuite pour produire des photographies bien construites, où l’œil peut se « promener », sont innombrables. === Où faut-il placer les points de fuite ? === Lorsque c'est possible, les photographes aiment bien utiliser les lignes fuyantes qui, lorsqu'elles sont présentes, présentent toujours un caractère attractif ; cependant, la position du point de fuite n'est pas anodine car elle conditionne la façon dont le regard va découvrir l'image. * Faites des essais, expérimentez ! Avec deux rails fuyant au loin on peut déjà imaginer plusieurs possibilités. Par exemple, se placer exactement dans l'axe et situer le point de fuite au beau milieu du cadre pour jouer au maximum sur la symétrie. Ou encore, utiliser l'une ou l'autre des diagonales, ce qui donnera selon celle que l'on aura choisi des aspects dynamiques très différents. N'hésitez pas à vous déplacer pour rechercher le meilleur point de vue. * Préférez les objectifs grand angulaires qui vous permettent de situer dans le champ la plus grande partie possible des éléments convergents, surtout lorsque vous vous situez au milieu des lignes convergentes et non sur le côté. La photographie gagnera d'autant plus en profondeur que la focale sera plus courte. * Soignez le point d'arrivée des lignes. Il n'est pas absurde de le situer au niveau de l'un des points forts de l'image, selon la « règle des tiers ». L'idéal est que des éléments intéressants soient placés au niveau de ce point de fuite, de façon que les lignes y conduisent le regard. Il est possible aussi de faire converger les lignes hors du cadre, ce qui conduit inévitablement le regard à sortir de l'image. Le fait de ne pas savoir ce qui se trouve au bout des lignes peut être frustrant pour le spectateur, parfois aussi cela donne une impression de déséquilibre néfaste à la perception de l'image. === Exemples pratiques === {| border="1" cellpadding="5" cellspacing="0" |- |[[Image:CarlvonLinne Garden.jpg|240px|center]] |'''Jardin Linné à Uppsala'''<br /><br /> Le photographe a choisi de placer la ligne d'horizon vers le haut de la photographie, le choix d'une perspective légèrement plongeante correspond au choix de montrer plutôt le jardin que le ciel. Le triangle constitué par l'allée, renforcé par le fronton du bâtiment, nous invite à la visite. |- |[[Image:Hagel in Finnland.jpg|240px|center]] |'''Pluie en Finlande'''<br /><br /> Hé oui, on peut aussi faire des photos par temps de pluie. Celle-ci produit une impression de déséquilibre pour plusieurs raisons, d'abord elle est nettement penchée, ensuite le point de fuite de la partie avant de la route est hors du cadre, puis la route, après avoir en quelque sorte fait une fugue en-dehors de l'image, repart vers un autre point de fuite physiquement présent. |- |[[Image:TokyoHighways1084.jpg|240px|center]] |'''Autoroute au centre de Tokyo'''<br /><br /> Ici le point de fuite principal ne provient pas de lignes droites mais de courbes qui convergent vers la droite de l'image. Cette impression de convergence est accentuée par la présence des lignes blanches tracées au sol et des automobiles qui se dirigent vers la droite. |- |[[Image:Nt-castelo-belmonte8.jpg|160px|center]] |'''Château de Belmonte, Portugal'''<br /><br /> La chaussée vivement éclairée est une zone claire vers laquelle se dirige immédiatement le regard. On attend ici ce qui pourrait bien venir de la partie cachée. En retournant la photo droite-gauche, on aurait une toute autre impression, provoquée par la curiosité de suivre nous-mêmes ce chemin pour voir ce qui est derrière le mur. C'est l'exemple typique d'une photo qui, vue dans un sens ou dans l'autre, aura une signification différente en fonction des habitudes de lecture du pays. Noter que la limite brutale de l'ombre est un des éléments géométriques forts de cette photographie. Presque alignée avec le haut du mur qui la forme, elle sépare l'image en une partie éclairée et une partie sombre. |- |[[Image:New York 1999 1.jpg|240px|center]] |'''Pont de Brooklyn, New-York'''<br /><br /> Sur une perspective, deux droites sont parallèles quand elles ont le même point de fuite mais ici, ce sont des lignes divergentes qui deviennent parallèles, au moins sur une partie très visible de leur longueur. L'œil a évidemment du mal à y croire et l'impression produite est curieuse. |- |[[Image:Stromschiene Hamburger S-Bahn.jpg|240px|center]] |'''Voie ferrée en Allemagne'''<br /><br /> En l'absence de tout repère vertical, on peut facilement montrer que cette photographie est penchée : nous sommes dans une portion de ligne droite, le dévers de la voie est nul, celle-ci est dans un plan horizontal. Son point de fuite et celui des traverses devraient se trouver tous deux au même niveau sur la ligne d'horizon, or le second est beaucoup plus haut que le premier. Cette inclinaison vers la droite est dans le même sens que la convergence des rails et cela donne à cette photo, dont le but est au départ uniquement documentaire, un certain dynamisme. |- |[[Image:Loretto Chapel.jpg|160px|center]] |'''Loretto Chapel, Nouveau-Mexique, États-Unis'''<br /><br /> Ce superbe escalier en hélice cylindrique donne un bel effet d'élan vers le haut. |- |[[Image:Uberlândia-MG 002- 30.jpg|240px|center]] |'''Brésil'''<br /><br /> Curieuse photographie prise depuis une automobile en train de dépasser un autocar. Mal de mer assuré ! |- |[[Image:Sagrada_Familia_2003_DSCN0107.jpg|240px|center]] |'''Barcelone, Sagrada Familia'''<br /><br /> Les flèches des grues contrebalancent légèrement le basculement des tours mais le parti pris de cette inclinaison reste discutable. |- |[[Image:C-141 Starlifter contrail.jpg|160px|center]] |'''Starlifter C-141 au-dessus de l'Antarctique'''<br /><br /> Un éclairage fabuleux et les traînées de vapeur judicieusement placées donnent une impression de mouvement et de puissance. |- |[[Image:Sundial Bridge and cables (35034359904).jpg|160px|center]] |'''Pont et câbles'''<br /><br /> Une photographie très graphique sur fond de ciel animé. |- |} == Répétition d'objets == {| border="1" cellpadding="5" cellspacing="0" |- |[[Image:Sampietrini.jpg|240px|center]] |'''Pavés'''<br /><br /> La répétition d'un motif périodique donne des images de plus en plus petites au fur et à mesure de l'éloignement. Les droites parallèles convergent vers un point de fuite situé en-dehors du cadre. En plaçant ce point juste dans le coin en haut et à droite, cela aurait donné une composition en diagonale beaucoup plus forte mais ... le pavage ne s'étend pas jusqu'à l'infini et il était sans doute impossible de faire autrement. |- |[[Image:Roof Japan.jpg|240px|center]] |'''Toit au Japon'''<br /><br /> Même processus que précédemment mais cette fois le point de fuite est pratiquement dans l'axe de la photographie. La structure est plus forte que pour les pavés, qui ne forment que des lignes, puisqu'on a ici un quadrillage. Les bords des tuiles donnent des lignes parallèles aux bords horizontaux du cadre, leur point de fuite est repoussé à l'infini. |- |[[Image:Alligator Heads.jpg|240px|center]] |'''Têtes d'alligators'''<br /><br /> L'impression de profondeur n'est pas très importante ici mais tel n'était peut-être pas le but du photographe. Pour l'augmenter, il aurait sans doute fallu s'approcher un peu plus et choisir une distance focale plus courte pour changer la perspective. Les taches colorées qui subsistent dans les deux coins à droite perturbent le regard, il faudrait les supprimer. |- |[[Image:Chaises-saint-denis.JPG|240px|center]] |'''Mairie de Saint-Denis de la Réunion'''<br /><br /> L'alignement presque parfait de ces chaises et une prise de vue dans une direction oblique par rapport aux rangées donne ici un grand nombre de lignes de fuite le long desquelles se répète le motif des dossiers, vus de plus en plus petits. Le sujet est relativement banal mais le photographe en a très bien tiré parti. |- |[[Image:Roma - Basilia di San Paolo fuori le mura 6.JPG|240px|center]] |'''Rome, basilique Saint-Paul hors les murs'''<br /><br /> Ici ce sont les caissons du plafond qui se répètent mais l'effet n'est pas très marqué. Peut-être aurait-il mieux valu montrer davantage de caissons ou au contraire concentrer l'attention du spectateur sur un seul. En photographie comme en d'autres domaines, les solutions de compromis ne sont pas toujours les meilleures. |- |} == Choix de focales extrêmes == == Usage de la [[profondeur de champ]] == <gallery widths="240px" heights="240px"> Pinet_via_Domitia.jpg|'''La Via domitia à Pinet (France)'''<br />Les bords du chemin, qui est une ancienne voie romaine, convergent vers les personnages qui apportent un élément de vie dans le paysage. Il était important que le premier plan, somme toute relativement vide, soit le plus net possible en même temps que les plans éloignés ; autrement, on aurait une vaste plage floue du plus mauvais effet. Ici le cheminement du regard est presque évident : d'abord attiré par la tache claire que constitue le chemin, il s'éloigne ensuite jusqu'à découvrir les détails situés au voisinage du point de fuite. Рибното езеро обгърнато от минзухари.jpg </gallery> == Utilisation des couleurs chaudes et des couleurs froides == == Utilisation des ombres == {| border="1" cellpadding ="5" cellspacing="0" |- |[[Image:Shadows-in-the-sand.jpg|Shadows at the beach|240px|center]] | '''Ombres sur le sable''' : La plage est vide mais la lumière venant de dos excite l'imagination de deux joyeux lurons qui trouvent ainsi l'occasion de meubler un espace vide. |- |} == Usage de la diffusion atmosphérique == La diffusion atmosphérique est souvent une aide appréciable pour rendre l'impression de profondeur mais on ne peut la considérer comme sympathique que si elle est due à des causes naturelles et non à la pollution. Elle est parfois aussi une gène importante, en particulier dans le domaine de la photographie aérienne. {| border="1" cellpadding="5" cellspacing="0" |- |[[Image:Hamburg.Highflyer.Speicherstadt.wmt.jpg|160px|center]] |'''Port de Hambourg'''<br /><br /> Cas typique de lointains estompés par le voile atmosphérique. Ici ce voile est relativement léger, il ne fait pas disparaître les détails ni la netteté de l'image mais il diminue les contrastes et donne des tonalités bleutées et froides à la photographie. |- |[[Image:Shuanlo2.jpg|240px|center]] |'''Taichung City (Taïwan)'''<br /><br /> Contrairement à la précédente, la vue comporte ici la ligne d'horizon et le ciel est également présent. La lumière est beaucoup plus belle et cette image se lit, pour nous autres Européens, « dans le bon sens » : entendons par là que le regard se pose d'abord sur les zones claires, qui se trouvent en haut et à gauche, puis arrive sur les zones plus sombres mais aussi plus détaillées en bas et à droite. La composition pratiquement en diagonale comporte une zone relativement banale au-dessous à gauche et une autre beaucoup plus intéressante au-dessus à droite. |- |[[Image:Pittsburgh16.jpg|240px|center]] |'''Pittsburgh'''<br /><br /> La ville est vue ici de beaucoup plus près et un peu de vie apparaît. Malgré cela, l'ambiance ne semble pas d'une gaieté folle et sans les personnages elle deviendrait carrément sinistre ! |- |[[Image:Foggy Portland Morning.jpg|240px|center]] |'''Matin de brume à Portland'''<br /><br /> Ambiance froide, peu d'animation, mais la profondeur est là. |- |[[Image:Incense-LE.jpg|240px|center]] |'''Encens à Wat Phra Kaeo'''<br /><br /> Un excellent jeu de lumière sur la fumée de l'encens et sur les personnages donne ici une photographie fort attachante. |- |[[Image:Meat section of the market in Oaxaca.jpg|240px|center]] |'''Le secteur de la viande au marché d'Oaxaca'''<br /><br /> Ambiance brumeuse et enfumée dans ce marché, ce qui réduit la confusion des plans qui se produit trop souvent pour ce genre de scène et par la même occasion, adoucit les lumières. Géométriquement, c'est par ailleurs une perspective frontale quasi parfaite. |- |[[Image:Nebel-Allee Fog.jpg|240px|center]] |'''Brouillard sur la route près de Leutkirch, Bavière, Allemagne'''<br /><br /> On comprend bien que les lignes blanches sont utiles pour la perspective mais elles doivent l'être encore plus pour les automobilistes souvent confrontés au brouillard dans cette région. L'impression fournie par la brume disparaît à partir d'une certaine distance, car tout finit par se confondre. Les nuances subtiles apportées par la couleur justifient que l'on ne transforme pas cette photographie en noir et blanc. |- |[[Image:Nebelwald.jpg|160px|center]] |'''Nebelwald im Sauerland''' (forêt brumeuse dans le Sauerland)<br /><br /> L'effet de la perspective plafonnante se conjugue à celui de la brume mais les deux effets sont très différents. La brume permet de séparer les divers plans de l'image tandis que la perspective plafonnante suggère que les arbres atteignent ici une hauteur très importante. On peut juste regretter que la photographie penche vers la droite. |- |[[Image:Climbing Pucon 03.jpg|240px|center]] |'''Ascension du Pucon, Chili'''<br /><br /> Ambiance pour le moins feutrée pour cette ascension, qui nécessite un bon guide ! La profondeur de l'espace où l'on peut encore distinguer quelque chose est ici très limitée. |- |[[Image:Fort Point under the Golden Gate Bridge.JPG|240px|center]] |'''Pont du Golden Gate'''<br /><br /> Il est apparemment fréquent que cet ouvrage soit pris dans la brume et il l'est ici ni trop, ni trop peu, puisque la zone visible s'étend jusqu'à l'autre rive. Un peu plus de lumière sur cette brume aurait été bienvenue et l'on peut surtout regretter, une fois de plus, que cette photo soit penchée. Il devait être possible, en tenant l'appareil droit et en se déplaçant un peu, d'obtenir une belle composition en diagonale, éventuellement en retaillant un peu le haut et le bas de la photographie. Dommage ! |- |} <gallery> Image:Unos30.jpg </gallery> == Étagement des plans, usage de premiers plans == Lorsque tous les éléments d'un sujet se trouvent dans le même plan, les photographies qui en résultent paraissent « plates », sans relief. La présence d'éléments situés à des distances différentes permet au contraire d'obtenir une impression de « profondeur » qui compense dans une certaine mesure le fait que l'image n'a que deux dimensions. {{EnTravaux}} <gallery widths="240px" heights="240px"> Mountain view 06.jpg|Une situation très courante en montagne : le photographe se trouve sur le flanc plus ou moins escarpé d'une vallée et photographie le versant opposé. L'élément le plus proche du paysage se trouve à plusieurs centaines de mètres, voire à plusieurs kilomètre, l'image résultante est généralement plate, monotone, voire ennuyeuse. KURANGANI TO MELMUTTAM TREK 2.jpg|Ici la situation est analogue mais la lumière latérale sculpte quelque peu le paysage et révèle légèrement les reliefs. Wetterhorn - img 35498.jpg Guarda-sol azul numa praia portuguesa (2621234841).jpg Беглика язовир.jpg North Window at dusk in Arches National Park. NPS-Kirsten Leong (18497714388).jpg Ibirapuera (2710550080).jpg Phewa Lake at sunset.jpg Monument Valley 2.jpg Минзухарен килим.jpg Duna-part, szemben Visegrád. Fortepan 18953.jpg USS PAHAD KE PEECHHEY PARI RAHTI HAI.jpg Chitkul IMG 20160427 114408.jpg Schweiger pils - 2.jpg Summer is coming (Unsplash).jpg </gallery> {| |- |[[Image:|300px|center]] | |- |} == Usage de miroirs == == Mise à profit du mouvement == == Utilisation des regards == == Formation d'une troisième image == == Photographies diverses == {| border="1" cellpadding="5" cellspacing="0" |- |[[Image:TenryujiBuilding.jpg|240px|center]] |'''Feuilles d'automne à Tenryuji, Kyoto, Japan'''<br /><br /> L'impression de profondeur procurée par cette image tient à plusieurs facteurs : la présence d'un élément rouge au premier plan, devant un décor aux couleurs plutôt froides, les lignes fuyantes et la faible profondeur de champ qui permet de bien détacher le feuillage devant l'arrière-plan flou. |- |} {{Ph Composition}} qrmjzmyzxjz8ms4dfqert1y0euyiodb Discussion utilisateur:DavidL 3 26158 765978 765936 2026-05-04T17:34:37Z DavidL 1746 /* Demande de conseil */ 765978 wikitext text/x-wiki {{/Onglets}} {| class="flexible" style="width:100%;" | style="vertical-align: top;" | <div style="padding: 20px; font-size: 120%; border: thin solid #A7D7F9;"> [[Image:Chat bubbles.svg|123px|right]] Bienvenue sur ma page de discussion. <!-- le lien "ajouter un message" --> <div class="noprint plainlinks" style="background-color: #9292924f; width: 300px; margin-top: 30px; margin-left: auto; margin-right: auto; padding: 5px 25px 5px 25px; text-align: center; border: 1px solid #9292924f; font-size:12pt; -moz-border-radius:5px; -webkit-border-radius:5px; -o-border-radius:5px; border-radius:5px; background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#00000000), to(#93939328)); background:-moz-linear-gradient(0% 90% 90deg, #93939328, #00000000);"> [{{fullurl:Discussion utilisateur:DavidL|action=edit&section=new}} Ajouter un message] </div> {{clr}} </div> | style="vertical-align: top;" | __TOC__ |} <!--{{OngFin}}--> <!-- *************************************** --> == Salut David == Ça fait un bail ! Merci pour ta correction dans le chapitre consacré à la création des projets de mon bouquin sur le Mouvement Wikimédia. J'en profite pour te dire que le livre devrait, enfin et si tout se déroule comme prévu, être publié au format de poche en début de cette nouvelle année. Raison pour laquelle je suis en train de tout relire et remettre en forme tout en corrigeant pas mal d'informations parfois fausses ou en tout cas mal formulées. Dans ce travail de relecture, j'ai découvert que l'outil de création automatique d'une référence au départ de l'URL d'une page ne fonctionne pas sur Wikilivre. Tu vois ce dont je parle ? C'est une option du menu qui apparait dans l'éditeur visuel. Quand tu cliques sur « sourcer » un encart te proposes d'introduite une référence automatiquement, manuellement, ou de dupliquer le renvoi vers une référence déjà existante. Et bien sur Wikilivres, la fonction automatique ne fonctionne pas pour l'instant, alors qu'elle fonctionne sur Wikipédia et Wikiversité, là où je crée mes références avant de les copier-coller dans Wikilivres en attendant que le problème soit résolut. As-tu du temps pour voir ce qui pose un problème ? Moi, je suis un peu à la bourre pour terminer ma relecture à temps... [[User:Lionel Scheepmans|Lionel Scheepmans]] <sup><big>✉</big> [[User talk:Lionel Scheepmans|Contact]]</sup> <sub>Désolé pour ma [[w:dysorthographie|dysorthographie]], [[w:dyslexie|dyslexie]] et [[wikt:distraction|"dys"traction]].</sub> 3 janvier 2026 à 18:03 (CET) :Salut Lionel :J'ai testé : j'obtiens un message d'avertissement dans la console : : « Mapping(s) missing from citoid-template-type-map.json: dataset, preprint, standard » : « Empty template Lien web produced. Check this template has a correctly configured citoid map in the template data. » :et également une erreur de requête HTTP dans la réponse en JSON : « error "Unable to retrieve data from ..." » :Il doit y avoir une différence quelque part dans [[Module:Biblio|le module Biblio]] avec wikipédia. :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 4 janvier 2026 à 00:39 (CET) ::Je vois que tu as plus de compétence que moi en la matière. Je voudrais bien développé mes connaissances en informatique, mais dela demande du temps, de la motivation et un certaine organisation. Merci pour ton aide ! [[User:Lionel Scheepmans|Lionel Scheepmans]] <sup><big>✉</big> [[User talk:Lionel Scheepmans|Contact]]</sup> <sub>Désolé pour ma [[w:dysorthographie|dysorthographie]], [[w:dyslexie|dyslexie]] et [[wikt:distraction|"dys"traction]].</sub> 4 janvier 2026 à 04:41 (CET) == Texte indigeste == Salut David, Je n'ai pas compris ta dernière modif sur [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC]], en quoi est-ce indigeste ? [[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 17 mars 2026 à 08:51 (CET) :Salut Alex, :Il s'agit du code wiki de la page qui n'est pas du tout lisible, il y a plus de code HTML que de contenu. Alors qu'il faudrait utiliser le style commun, ne pas faire du formatage systématique, utiliser les modèles standards pour avoir un code wiki simple. :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 17 mars 2026 à 08:54 (CET) ::Salut David, ::A vrai dire, je n'ai utilisé que le strict nécessaire pour rendre la page la plus lisible possible, est-ce que tu as des exemples de structuration de code en tête pour le rendre plus clair ? [[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 18 mars 2026 à 08:59 (CET) :::La page [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC]] fait 1M d'octets. :::Il faudrait la diviser en sous pages, supprimer le formatage pour utiliser une syntaxe wiki claire, éviter les boîtes déroulantes, les défilements dans des zones de hauteur limitée. :::Les autres livres ont un texte source clair, et sont de taille raisonnable : :::* [[Programmation Java/Commentaires]] 2383 octets, :::* [[Étude scientifique de la technique du piano/L’étude scientifique de la technique]] 36907 octets, :::* [[Photographie]] 7898 octets, :::* [[Photographie/Conseils aux débutants/Acheter un appareil numérique]] 27852 octets. :::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 18 mars 2026 à 19:12 (CET) :::: Bonjour David, :::: Pour les pages [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Textes et traductions Ier millénaire AEC]] et [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_EC|Textes et traductions Ier millénaire EC]], je n'ai uniquement utilisé l'éditeur wikicode et jamais d'éditeur html. Par contre, pourquoi ne peut-on pas utiliser la mise en forme que l'on souhaite sur Wikibooks ? Sur les autres portails je comprends la nécessité de standardisation, mais les livres sur Wikibooks sont tous différents... [[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 30 avril 2026 à 16:57 (CET) :::::Bonjour Alex, :::::Les livres sont tous différents en mises en forme, mais il y a des règles et recommandations à respecter, notamment [[Aide:Syntaxe#Les erreurs à éviter|ne pas abuser du HTML et privilégier la syntaxe wiki]]. Les pages doivent pouvoir être mises à jour par tous. Il faut notamment utiliser [[Wikilivres:Modèles|les modèles disponibles]] plutôt que d'inventer une mise en forme. Il faut surtout ne pas utiliser le copier-coller, mais soit utiliser un modèle existant, soit en créer de nouveaux pour les utiliser de nombreuses fois dans les pages ; par exemple, cela permet de modifier le style de tous les paragraphes en un seul endroit : dans le modèle. :::::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 30 avril 2026 à 19:14 (CEST) == Merci David == j'ai observé que tu avais mis mon livre le "miroir brisé" ...... sur l'établi MERCI et bien cordialement :-) [[Utilisateur:Clopeau|Clopeau]] ([[Discussion utilisateur:Clopeau|discussion]]) 18 mars 2026 à 07:22 (CET) == You may be an eligible candidate for the U4C election == <div lang="en" dir="ltr" class="mw-content-ltr"> Greetings, The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee (U4C)]] seeks candidates for the 2026 election. The U4C is the global committee responsible for overseeing enforcement of the [[foundation:Special:MyLanguage/Policy:Universal Code of Conduct|Universal Code of Conduct]]. Elections are held annually, if elected a committee member serves for two years. This year the U4C requires candidates to hold administrator rights on at least one wiki, which is why you are being contacted as you appear to hold this right. There are other requirements, such as candidates must be at least 18 years old and may not be employed by the Wikimedia Foundation or other related chapters and affiliates. You can find more information in the [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026#Call_for_Candidates|call for candidates on Meta-wiki]]. Additionally, the committee's working language is English; some ability to communicate in English is required. The election opens on 18 May, if you are eligible and interested you have until 10 May to submit your candidacy. There will week between for candidates to answer questions from the community. Voting takes place privately in [[m:Special:MyLanguage/SecurePoll|SecurePoll]], successful candidates must receive at least 60% support. More information is available on [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|the 2026 Elections page]], including timelines and other candidacy information. If you read over the material and consider yourself qualified, please consider submitting your name to run for the committee. If you think someone else in your community might be interested and qualified, please encourage them to run. In partnership with the U4C -- [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User_talk:Keegan (WMF)|talk]]) 28 avril 2026 à 20:33 (CEST) </div> <!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Keegan_(WMF)/test&oldid=30471754 --> == Demande de conseil == Dans la page : [[Bonheur, morale, et philosophie épistolaire]] L'auteur à utilisé [https://fr.wikibooks.org/w/index.php?title=Dossier_sur_l%27amiti%C3%A9_(cliquer)&redirect=no/ Dossier sur l'amitié (cliquer)] <nowiki>https://fr.wikibooks.org/w/index.php?title=Dossier_sur_l%27amiti%C3%A9_(cliquer)&redirect=no/</nowiki> Dossier sur l'amitié (cliquer) Au lieu d'utiliser simplement: * Dossier sur l'amitié (cliquer)| Dossier sur l'amitié (cliquer) Le résultat est que la page [[ Dossier sur l'amitié (cliquer)]] est dans les pages orphelines. Est-ce que je peux modifier ce code, pour faire sortir cette page des pages orphelines ? Merci. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 4 mai 2026 à 10:03 (CEST) :Salut Xhungab, :Oui, n'hésites pas à effectuer les modifications nécessaires. :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 4 mai 2026 à 19:34 (CEST) rfco67zq09ybh867s39hir00vlk2rrk 766011 765978 2026-05-04T21:16:53Z Xhungab 23827 766011 wikitext text/x-wiki {{/Onglets}} {| class="flexible" style="width:100%;" | style="vertical-align: top;" | <div style="padding: 20px; font-size: 120%; border: thin solid #A7D7F9;"> [[Image:Chat bubbles.svg|123px|right]] Bienvenue sur ma page de discussion. <!-- le lien "ajouter un message" --> <div class="noprint plainlinks" style="background-color: #9292924f; width: 300px; margin-top: 30px; margin-left: auto; margin-right: auto; padding: 5px 25px 5px 25px; text-align: center; border: 1px solid #9292924f; font-size:12pt; -moz-border-radius:5px; -webkit-border-radius:5px; -o-border-radius:5px; border-radius:5px; background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#00000000), to(#93939328)); background:-moz-linear-gradient(0% 90% 90deg, #93939328, #00000000);"> [{{fullurl:Discussion utilisateur:DavidL|action=edit&section=new}} Ajouter un message] </div> {{clr}} </div> | style="vertical-align: top;" | __TOC__ |} <!--{{OngFin}}--> <!-- *************************************** --> == Salut David == Ça fait un bail ! Merci pour ta correction dans le chapitre consacré à la création des projets de mon bouquin sur le Mouvement Wikimédia. J'en profite pour te dire que le livre devrait, enfin et si tout se déroule comme prévu, être publié au format de poche en début de cette nouvelle année. Raison pour laquelle je suis en train de tout relire et remettre en forme tout en corrigeant pas mal d'informations parfois fausses ou en tout cas mal formulées. Dans ce travail de relecture, j'ai découvert que l'outil de création automatique d'une référence au départ de l'URL d'une page ne fonctionne pas sur Wikilivre. Tu vois ce dont je parle ? C'est une option du menu qui apparait dans l'éditeur visuel. Quand tu cliques sur « sourcer » un encart te proposes d'introduite une référence automatiquement, manuellement, ou de dupliquer le renvoi vers une référence déjà existante. Et bien sur Wikilivres, la fonction automatique ne fonctionne pas pour l'instant, alors qu'elle fonctionne sur Wikipédia et Wikiversité, là où je crée mes références avant de les copier-coller dans Wikilivres en attendant que le problème soit résolut. As-tu du temps pour voir ce qui pose un problème ? Moi, je suis un peu à la bourre pour terminer ma relecture à temps... [[User:Lionel Scheepmans|Lionel Scheepmans]] <sup><big>✉</big> [[User talk:Lionel Scheepmans|Contact]]</sup> <sub>Désolé pour ma [[w:dysorthographie|dysorthographie]], [[w:dyslexie|dyslexie]] et [[wikt:distraction|"dys"traction]].</sub> 3 janvier 2026 à 18:03 (CET) :Salut Lionel :J'ai testé : j'obtiens un message d'avertissement dans la console : : « Mapping(s) missing from citoid-template-type-map.json: dataset, preprint, standard » : « Empty template Lien web produced. Check this template has a correctly configured citoid map in the template data. » :et également une erreur de requête HTTP dans la réponse en JSON : « error "Unable to retrieve data from ..." » :Il doit y avoir une différence quelque part dans [[Module:Biblio|le module Biblio]] avec wikipédia. :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 4 janvier 2026 à 00:39 (CET) ::Je vois que tu as plus de compétence que moi en la matière. Je voudrais bien développé mes connaissances en informatique, mais dela demande du temps, de la motivation et un certaine organisation. Merci pour ton aide ! [[User:Lionel Scheepmans|Lionel Scheepmans]] <sup><big>✉</big> [[User talk:Lionel Scheepmans|Contact]]</sup> <sub>Désolé pour ma [[w:dysorthographie|dysorthographie]], [[w:dyslexie|dyslexie]] et [[wikt:distraction|"dys"traction]].</sub> 4 janvier 2026 à 04:41 (CET) == Texte indigeste == Salut David, Je n'ai pas compris ta dernière modif sur [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC]], en quoi est-ce indigeste ? [[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 17 mars 2026 à 08:51 (CET) :Salut Alex, :Il s'agit du code wiki de la page qui n'est pas du tout lisible, il y a plus de code HTML que de contenu. Alors qu'il faudrait utiliser le style commun, ne pas faire du formatage systématique, utiliser les modèles standards pour avoir un code wiki simple. :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 17 mars 2026 à 08:54 (CET) ::Salut David, ::A vrai dire, je n'ai utilisé que le strict nécessaire pour rendre la page la plus lisible possible, est-ce que tu as des exemples de structuration de code en tête pour le rendre plus clair ? [[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 18 mars 2026 à 08:59 (CET) :::La page [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC]] fait 1M d'octets. :::Il faudrait la diviser en sous pages, supprimer le formatage pour utiliser une syntaxe wiki claire, éviter les boîtes déroulantes, les défilements dans des zones de hauteur limitée. :::Les autres livres ont un texte source clair, et sont de taille raisonnable : :::* [[Programmation Java/Commentaires]] 2383 octets, :::* [[Étude scientifique de la technique du piano/L’étude scientifique de la technique]] 36907 octets, :::* [[Photographie]] 7898 octets, :::* [[Photographie/Conseils aux débutants/Acheter un appareil numérique]] 27852 octets. :::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 18 mars 2026 à 19:12 (CET) :::: Bonjour David, :::: Pour les pages [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Textes et traductions Ier millénaire AEC]] et [[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_EC|Textes et traductions Ier millénaire EC]], je n'ai uniquement utilisé l'éditeur wikicode et jamais d'éditeur html. Par contre, pourquoi ne peut-on pas utiliser la mise en forme que l'on souhaite sur Wikibooks ? Sur les autres portails je comprends la nécessité de standardisation, mais les livres sur Wikibooks sont tous différents... [[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 30 avril 2026 à 16:57 (CET) :::::Bonjour Alex, :::::Les livres sont tous différents en mises en forme, mais il y a des règles et recommandations à respecter, notamment [[Aide:Syntaxe#Les erreurs à éviter|ne pas abuser du HTML et privilégier la syntaxe wiki]]. Les pages doivent pouvoir être mises à jour par tous. Il faut notamment utiliser [[Wikilivres:Modèles|les modèles disponibles]] plutôt que d'inventer une mise en forme. Il faut surtout ne pas utiliser le copier-coller, mais soit utiliser un modèle existant, soit en créer de nouveaux pour les utiliser de nombreuses fois dans les pages ; par exemple, cela permet de modifier le style de tous les paragraphes en un seul endroit : dans le modèle. :::::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 30 avril 2026 à 19:14 (CEST) == Merci David == j'ai observé que tu avais mis mon livre le "miroir brisé" ...... sur l'établi MERCI et bien cordialement :-) [[Utilisateur:Clopeau|Clopeau]] ([[Discussion utilisateur:Clopeau|discussion]]) 18 mars 2026 à 07:22 (CET) == You may be an eligible candidate for the U4C election == <div lang="en" dir="ltr" class="mw-content-ltr"> Greetings, The [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee (U4C)]] seeks candidates for the 2026 election. The U4C is the global committee responsible for overseeing enforcement of the [[foundation:Special:MyLanguage/Policy:Universal Code of Conduct|Universal Code of Conduct]]. Elections are held annually, if elected a committee member serves for two years. This year the U4C requires candidates to hold administrator rights on at least one wiki, which is why you are being contacted as you appear to hold this right. There are other requirements, such as candidates must be at least 18 years old and may not be employed by the Wikimedia Foundation or other related chapters and affiliates. You can find more information in the [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026#Call_for_Candidates|call for candidates on Meta-wiki]]. Additionally, the committee's working language is English; some ability to communicate in English is required. The election opens on 18 May, if you are eligible and interested you have until 10 May to submit your candidacy. There will week between for candidates to answer questions from the community. Voting takes place privately in [[m:Special:MyLanguage/SecurePoll|SecurePoll]], successful candidates must receive at least 60% support. More information is available on [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|the 2026 Elections page]], including timelines and other candidacy information. If you read over the material and consider yourself qualified, please consider submitting your name to run for the committee. If you think someone else in your community might be interested and qualified, please encourage them to run. In partnership with the U4C -- [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User_talk:Keegan (WMF)|talk]]) 28 avril 2026 à 20:33 (CEST) </div> <!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Keegan_(WMF)/test&oldid=30471754 --> == Demande de conseil == Dans la page : [[Bonheur, morale, et philosophie épistolaire]] L'auteur à utilisé [https://fr.wikibooks.org/w/index.php?title=Dossier_sur_l%27amiti%C3%A9_(cliquer)&redirect=no/ Dossier sur l'amitié (cliquer)] <nowiki>https://fr.wikibooks.org/w/index.php?title=Dossier_sur_l%27amiti%C3%A9_(cliquer)&redirect=no/</nowiki> Dossier sur l'amitié (cliquer) Au lieu d'utiliser simplement: * Dossier sur l'amitié (cliquer)| Dossier sur l'amitié (cliquer) Le résultat est que la page [[ Dossier sur l'amitié (cliquer)]] est dans les pages orphelines. Est-ce que je peux modifier ce code, pour faire sortir cette page des pages orphelines ? Merci. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 4 mai 2026 à 10:03 (CEST) :Salut Xhungab, :Oui, n'hésites pas à effectuer les modifications nécessaires. :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 4 mai 2026 à 19:34 (CEST) Merci. Je vais faire le travail [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 4 mai 2026 à 23:16 (CEST) 619u0k59o6cf7o7uzzvr73pqedqasns Photographie/Personnalités/G/Wilhelm von Gloeden 0 51037 765947 761555 2026-05-04T14:07:15Z Inertia6084 63342 ([[c:GR|GR]]) [[c:COM:FR|File renamed]]: [[File:Gloeden, Wilhelm von (1856-1931) - n. 1641 - Getty Museum.jpg]] → [[File:Gloeden, Wilhelm von (1856-1931) - n. 1641 - Nudo accademico maschile di fronte - Getty Museum - 18x24.jpg]] [[c:COM:FR#FR1|Criterion 1]] (original uploader’s request) 765947 wikitext text/x-wiki {{Ph s Personnalités}} [[Image:Wilhelm von Gloeden (c. 1891).jpg|thumb|Autoportrait (1891)]] Le baron '''Wilhelm von Gloeden''', né à Wismar (Mecklembourg) le 16 septembre 1856 et mort à Taormina le 16 février 1931, était un photographe allemand. == Biographie == Wilhelm von Gloeden est considéré comme l'un des plus grands photographes de [[nu]] masculin et aussi comme l'un des pionniers de la photographie en plein air. Après avoir étudié l'histoire de l'art à Rostock, il suivit une formation de peintre. En 1878, pour soigner sa tuberculose, il se rendit, sur le conseil de son médecin, à Taormina en Sicile. Le peintre Otto Geleng qui y vivait déjà lui avait parlé de ce lieu paradisiaque. Émerveillé par les paysages siciliens, mais surtout par la beauté sauvage et antique des jeunes paysans et pêcheurs de Taormina, von Gloeden s'initia à la photographie, aidé aussi bien par les photographes locaux que par son cousin [[Wilhelm von Plüschow]] qui vivait à Naples et qui était, lui aussi, fasciné par le charme des jeunes Italiens du sud. Von Gloeden devint rapidement célèbre pour ses clichés d'éphèbes aux poses très inspirées de l'art antique. Ses photographies de nu masculin dégagent une puissance érotique peu égalée. Il reçut vers 1900 la visite d'un autre futur grand photographe du genre, [[Rudolph Lehnert]]. Très rapidement il devint très apprécié des esthètes de son temps qui lui commandaient des clichés : les écrivains Anatole France, Gabriele D'Annunzio, Oscar Wilde, Marcel Proust, mais aussi Richard Strauss, le ''Kaiser'' Guillaume II d'Allemagne, le Konprinz, Édouard VII, le roi d'Angleterre qui popularisa le nudisme, et même le roi du Siam. Plusieurs de ses photographies furent exposées et publiées dans les plus grands magazines spécialisés, ce qui peut sembler étonnant dans le contexte homophobe de l'époque. Si de nombreuses photographies de von Gloeden exaltent le désir homosexuel, elles ont été tolérées du fait de l'alibi de l'héritage culturel grec et surtout, parce qu'aucun de ses clichés n'est pornographique. Au début de la guerre en 1914, von Gloeden décida de rentrer en Allemagne. Lorsqu'il revint à Taormina, il avait beaucoup perdu de son inspiration et un peu de son goût pour la photographie. Beaucoup de ses modèles avaient péri à la guerre et les contraintes des normes sociales étaient désormais plus dures. Il mourut en 1931 et fut enterré près de sa sœur dans le cimetière protestant de Taormina. L'un de ses fidèles modèles et ami, Pancrazio Bucini, surnommé ''Il Moro'', hérita du fonds photographique (probablement quelques 7 000 clichés). Ces documents furent saisis par les fascistes en 1933 et 1936, environ soixante pour cent fuent détruits, et ''Il Moro'' fut condamné pour détention de photographies pornographiques, avant d'être ultérieurement acquitté. Finalement, l'œuvre du Baron est reconnue comme œuvre d'art et son jeune protégé exonéré. Il parvint à récupérer environ 800 négatifs. À sa mort en 1963, ''Il Moro'' les laissa à son fils qui les vendit à un antiquaire. Depuis l'an 2000, le fonds von Gloeden se trouve au Musée Alinari de Florence. Les tirages collectionnés durant le 20{{e}} siècle par les amateurs du travail de von Gloeden sont heureusement nombreux, tout comme les [[cartes postales]] et les catalogues d'expositions. Son œuvre rencontre encore de nos jours un vif intérêt, notamment dans la communauté homosexuelle. [[Roland Barthes]] a préfacé une monographie de von Gloeden. Si aujourd'hui Taormina continue de s'enorgueillir du séjour de von Gloeden, elle n'est plus vraiment ce qu'elle était au temps du Baron Von Gloeden ni des ses héritiers tel [[Konrad Helbig]]. == Publications == * Taormina [préface de Roland Barthes] .- Pasadena, Twelvetrees Press, 1986. {{ISBN|0942642228|9780942642223}} == Bibliographie == * CANET, Nicole .- Von Gloeden, [[Vincenzo Galdi]], Von Pluschow, Poésies Arcadiennes. Paris, Éditions Thélès, 2003. {{ISBN|2-84776-212-4}}. * CANET, Nicole .- Gloedeneries caravagesques : Von Gloeden, Von Pluschow, Vincenzo Galdi, nus masculins .- Paris, Au Bonheur du Jour, 2005. {{ISBN|2-9523322-1-5}} * CANET, Nicole .- Wilhem Von Gloeden, [[Photographie/Personnalités/P/Wilhelm von Plüschow|Guglielmo Pluschow]], [[Vincenzo Galdi]], Paradis Siciliens, Paysages, Portraits et Nus 1890 19052008. {{ISBN|2-9523322-5-8}} * FALZONE BARBARO, MIRAGLIA, Marina et MUSSA, Italo [avec une note de Goffredo Parise] .- Le fotografie di Von Gloeden .- Milan, Longansesi, 1980, 157 p. [diverses éditions de 1996 à 2000] * LEMAGNY, Jean-Claude .- Taormina, début de siècle .- Paris, Chêne, 1975, 105 p. {{ISBN|2851080423|9782851080424}} * PEYREFITTE, Roger .- Wilhelm von Gloeden .- Paris, Éditions Textes Gais, juillet 2008, biographie et cahier de 50 nus masculins. {{ISBN|2914679300|978-2914679305}} * Wilhelm von Gloeden, Wilhelm von Pluschow, Vincenzo Galdi, Italienische Jûnglings-photographien um 1900 .- Berlin, Janssen Verlag, 1991, {{ISBN|3-925443-11-8}} * Wilhelm von Gloeden, Wilhelm von Plüschow, Vincenzo Galdi, [http://www.aubonheurdujour.net/Beautes_Siciliennes.html Beautés Siciliennes], Éditions Nicole Canet, 2014 {{ISBN|978-2-9532351-7-3}}. == Voir aussi == * Biographie complète et galerie photo : http://vongloedengayhistory.free.fr/index.html * Biographie complète et galerie photo : http://www.aubonheurdujour.net/Gloeden.html {{T|[[Commons:Category:Wilhelm von Gloeden|voir d'autres photographies de von Gloeden sur Wikimedia Commons]]}} {{T|[[Commons:Catalogue of Wilhelm von Gloeden's pictures|voir le catalogue des œuvres de Wilhelm von Gloeden sur Wikimedia Commons]]}} == Galerie de photographies == <gallery widths="240px" heights="240px"> Gloeden, Wilhelm von (1856-1931) - n. 0000 - Pescatore.jpg Gloeden, Wilhelm von (1856-1931) - n. 0003 - Beauté siciliennes, p. 111.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0003 - Siesta greca.jpg Gloeden, Wilhelm von (1856-1931) - n. 0004r - Frate e ciabattino - 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 0009 - Bimbo - Galerie Bassenge - da - Beautés siciliennes, p. 117.jpg Gloeden, Wilhelm von (1856-1931) - n. 0011 - Madonna.jpg Gloeden, Wilhelm von (1856-1931) - n. 0021 - Da Amore e arte.jpg Gloeden, Wilhelm von (1856-1931) - n. 0022 - Serenata siciliana - Inexhaustible Italy, National Geographic 06-12 1916.jpg Gloeden, Wilhelm von (1856-1931) - n. 0033 B - Beauté siciliennes, p. 102 - Timbro W. von Gloeden Napoli (2).jpg Gloeden, Wilhelm von (1856-1931) - n. G 0027 - Il teatro greco di Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 0036 - da - Sicilia mitica Arcadia - p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0037B - Taormina - Teatro greco - Beautés siciliennes, p. 121 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0040, Beautés siciliennes, p. 33.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._0045_-_Terra_del_Fuoco.jpg Gloeden, Wilhelm von (1856-1931) - n. 0050 B - Mandorli - recto.tif Gloeden, Wilhelm von (1856-1931) - n. 0053 - Beautés siciliennes, p. 97.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 - Capri - Bambine - Galerie Bassenge - Beautés siciliennes, p. 113.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - recto - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - verso - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057 - Due uomini nudi seduti su un masso.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057bis - Due uomini nudi di spalle - Cm 17x22,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 061 (deposé 1902) - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0067 - Pescatore - Beautés siciliennes, p. 95.jpg Gloeden, Wilhelm von (1856-1931) - n. 0063 - Da - Beautés siciliennes p. 36.jpg Gloeden, Wilhelm von (1856-1931) - n. 0064 - La confessione.jpg Gloeden, Wilhelm von (1856-1931) - n. 0065 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 0069 B & Crupi - Isola Bella e Capo Sant'Andrea - Perna p. 10.jpg Gloeden, Wilhelm von (1856-1931) - n. 0077 B - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0081 grande formato.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0088 - (Metropolitan museum).jpg Gloeden, Wilhelm von (1856-1931) - n. 0090 - Tomba di August von Platen a Siracusa.jpg|Tomba di August von Platen a Siracusa nel 1900 Gloeden, Wilhelm von (1856-1931) - n. 0099 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 0365 - Due ragazzi in giardino - Cm 18x22.jpg Gloeden, Wilhelm von (1856-1931) - n. 0110 B - Taormina - Teatro Greco con autoritratto - Beautés siciliennes, p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 0121.jpg Gloeden, Wilhelm von (1856-1931) - n. 0123 - Ragazzo abruzzese nudo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0125 - Posillipo - Timbro e data 8 marzo 1899. Perna, p. 94.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0126 - Drout auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0134 recto - Chiostro di Morreale a Palermo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0139 - Nerone. Da Perna, p. 20 - Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 0142 - Nerone Da Auch ich in Akadien, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 151 B - Taormina - Mandorli in fiore - Sehnsucht, p.68.jpg Gloeden, Wilhelm von (1856-1931) - n. 0152 - Debutdusiècle p. 29 & Leslie ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0174 - Twelwetrees p. 86 & Auch ich in Arkadien p. 125.jpg Gloeden, Wilhelm von (1856-1931) - n. 0185 - Beautés siciliennes, p. 114 - Campionario.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0210 recto - Hagar - Aste La Rosa.jpg Gloeden, Wilhelm von (1856-1931) - n. 0191, cm 18x24 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0195 - Perna p. 33.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0203 - Giovane arabo - Taschen p.79.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0208. Sigla BC e data 1909.jpg Gloeden, Wilhelm von (1856-1931) - n. 0222 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0224 - Peppino - Gallo, p. 17.jpg Gloeden, Wilhelm von (1856-1931) - n. 0225 - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - da - Amore e arte, p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - Les musiciens,1897 Tampon encré Gloeden au dos et date 1897.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 r - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 v - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0243 - 1899 - Da ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0246 - 1914 - Paesaggio taorminese.jpg Gloeden, Wilhelm von (1856-1931) - n. 0249 - Debutdesiècle p. 73.jpg Gloeden, Wilhelm von (1856-1931) - n. 0255 B - Sito Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1258 - Amore e arte, p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0263 - Caino, ca. 1902.jpg|''Caino'' Gloeden, Wilhelm von (1856-1931) - n. G 0263 - Caino.jpg Gloeden, Wilhelm von (1856-1931) - n. 0283.jpg Gloeden, Wilhelm von (1856-1931) - n. 0287 recto - Due giovani drappeggiati su un sentiero del Monte Ziretto - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0299 - Janssen, p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0307 - Janssen p. 23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 r - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 verso - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0313 recto - Peppino Scavo vestito alla greca - ex collection Levy - Cm 16x23 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0317 B recto - Peppino Scavo vestito alla greca - Cm 17,2x23,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 0347 recto - Timbrata a secco Gaetano Pedo, Roma.jpg Gloeden, Wilhelm von (1856-1931) - n. 0352 - Beautés siciliennes, p. 57b.jpg Gloeden, Wilhelm von (1856-1931) - n. 0354 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0358 - da - Auch ich in Arkadien, p. 167.jpg Gloeden, Wilhelm von (1856-1931) - n. 0396 B recto - Zannier, p. 141.jpg Gloeden, Wilhelm von (1856-1931) - n. 0339 - Gloedeneries caravagesques, 2005, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 0401 recto - Due ragazzi accanto a due barche a una nassa - Ebay, cm 13x18.png Gloeden, Wilhelm von (1856-1931) - n. 0405 - Piazza San Domenico - Archivio Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 0410 - Photogravure from Nus, printed by Buchverlag, 1926.jpg Gloeden, Wilhelm von (1856-1931) - n. 0114 recto - Giuseppe Capasso nel Teatro greco. Cm 11,5x15,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 0425 - da Et in Arcadia, p. 90.jpg Gloeden, Wilhelm von (1856-1931) - n. 0434 - Pietro di Napoli - Janssen p. 9.jpg Gloeden, Wilhelm von (1856-1931) - n. 0449 - Beautés siciliennes, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0445 recto - Due ragazzi nudi, con anfora e canna di bambù, accanto a un muro - Zannier p. 120, cm 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 0457 - Twelwetrees p. 88.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 recto - cm 17x23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 verso - Deponiert Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0474 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0437 recto - Due ragazzi nudi abbracciati.jpg Gloeden, Wilhelm von (1856-1931) - n. 1149 - Caputo, p. 81.jpg Gloeden, Wilhelm von (1856-1931) - n. 0545 - 24c - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0549 - Paradis sicilien, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0550 B - recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0557 B recto - Rimbaud 2 p. 5 & Perna p. 62.jpg Gloeden, Wilhelm von (1856-1931) - n. 0574 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0579.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 recto - Beautés siciliennes, p. 87.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 verso - Beautés siciliennes, p. 87 - Ristampato da Buciunì.jpg Gloeden, Wilhelm von (1856-1931) - n. 0611.jpg Gloeden, Wilhelm von (1856-1931) - n. 0621 recto - Ex Texbraun Collection, Galerie David Guiraud, Paris.jpg Gloeden, Wilhelm von (1856-1931) - n. 0645 - 4-2-1899 - Da - Barthes p. 46 & Auch ich in Arkadien p. 89.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 r - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 v - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0677.jpg Gloeden, Wilhelm von (1856-1931) - n. 0709 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 0717 - Siracusa latomie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0735 recto - Carlo Siligato e Pietro Mazza.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0127 recto - Gloedeneries p. 14.jpg Plüschow, Wilhelm von (1852-1930) - n. 0767 recto - Ragazzino nudo di spalle - Paradis sicilien, p. 43 - Napoli, Cm 11,5 x16,8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0769 - Twelvetrees p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0776 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0799 - Beautés siciliennes, p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0803 - da - Et in Arcadia ego, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0804 - Levante p. 47 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0831 recto - Pietro con un ramoscello sulle spalle, davanti a Mazzarò - Bonham auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. 0842 - Carlotta.jpg Gloeden, Wilhelm von (1856-1931) - n. 0845 - Gallo, p 8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0849 recto - Ragazzo drappeggiato, vestito da donna - Bassenge - Cm 17x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 0858 - Datato 12-9-1910 - Deposé 1903 - Cm 16,9x22, Turner p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0870 - Carlotta - 1890 ca. - Mussa, Italo - Wilhelm von Gloeden, ed. Malambri, 1980 p.....jpg Gloeden, Wilhelm von (1856-1931) - n. 0892 - Bimbo con stampella - Deponirt 1 Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0917 - Minerva auctions déc 2012.jpg Gloeden, Wilhelm von (1856-1931) - n. 0922 - Zannier, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0937 - Piccolo imperatore - da - Gallo, p.14.jpg Gloeden, Wilhelm von (1856-1931) - n. 0149 - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0996 - Twelvetrees p. 49.jpg Gloeden, Wilhelm von (1856-1931) - n. 1005.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 recto - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 verso - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1038 recto - Due uomini davanti a porta - Collezione privata.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._1039_-_da_-_Amore_e_arte,_p._92.jpg Gloeden, Wilhelm von (1856-1931) - n. 1050 recto - Quattro uomini nudi al mare - Schwulen Archiv Zürich - Cm 24x18.jpg Gloeden, Wilhelm von (1856-1931) - n. 1051 - Galerie au bonheur du jour ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1077 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1080 - Paradis sicilien, p. 51.jpg Gloeden, Wilhem von (1856-1931) - n. 1094 - Nudo maschile con cane.jpg Gloeden, Wilhelm von (1856-1931) - n. 1104 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1115 - Twelvetrees p. 40 & Perna p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 1117 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 - da - Auch ich in Arkadien, p. 165.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1160 (dated 1898).jpg Gloeden, Wilhelm von (1856-1931) - n. 1164.jpg Gloeden, Wilhelm von (1856-1931) - n. 1192 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1199 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1200 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1201 - Da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1211 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1228 - da - Auch ich in Arkadien, p. 178 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 1246 - Budapest library e Perna, p. 78.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 r - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 v - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1254 recto - Pasquale a torso nudo appoggiato a una roccia - Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1259 - 1899 - Taschen p.89.jpg Gloeden, Wilhelm von (1856-1931) - n. 1290 - Barthes p. 50 & Auch ich in Arkadien p. 169.jpg Gloeden, Wilhelm von (1856-1931) - n. 1310 - Timbrata - Beautés siciliennes, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 1348 - Napoli - Taschen p.51 e Pohlmann p. 140.jpg Gloeden, Wilhelm von (1856-1931) - n. 1355 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1360 - Barthes p. 52 e Auch ich in Arkadien, p. 106.jpg Gloeden, Wilhelm von (1856-1931) - n. 1365.jpg Gloeden, Wilhelm von (1856-1931) - n. 1368 - Deposé Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1369 - deponiert 20 oct. 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1398.jpg Gloeden, Wilhelm von (1856-1931) - n. 1403 recto - Donna in costume siciliano - Cm 16,9x22,4.jpg Gloeden, Wilhelm von (1856-1931) - n. 1459.jpg Gloeden, Wilhelm von (1856-1931) - n. 1519 recto - "Sguardo alla preda" - Déposé 17 Lug 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1526 - cm 22x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 1531 - Deposé 2 Jan 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1541 - deponirt 17 febr 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1590 - Adolescente laureato - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1600 dated 16 juin 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1610.jpg Gloeden, Wilhelm von (1856-1931) - n. 1628 - da - Auch ich in Arkadien, p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 1641 - Nudo accademico maschile di fronte - Getty Museum - 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 1703 - G. Pedo, 130 Via Sistina Roma. Deponirt 1 Aug. 1900 (Galerie Bassenge).jpg Gloeden, Wilhelm von (1856-1931) - n. 1721.jpg Gloeden, Wilhelm von (1856-1931) - n. 1731 - Maria Intelisano - 1905 ca. - da - Sicilia mitica arcadia, p. 64.jpg Gloeden, Wilhelm von (1856-1931) - n. 1733 recto - Profilo vago (Maria Intelisano).jpg Gloeden, Wilhem von (1856-1931) - n. 1735.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 recto - Ritratto di Giacomo Lanfranchi travestito da ragazza - Ebay - Cm 16,8x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 (colorata a mano).jpg Gloeden, Wilhelm von (1856-1931) - n. 1741 recto - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1744 - Hypnos.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 - (Hypnos) - Perna, p. 25 & Leslie Lohman faundation.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 A recto - Russel p. 73 e Beautés siciliennes p. 47.jpg Gloeden, Wilhelm von (1856-1931) - n. 1836 recto - Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1868 recto - Due adolescenti nudi affiancati contro un muro - Auctionsfr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1870 - da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 2095 recto - Autoritratto come Nazareno - Sehnsucht, p. 27.jpg Gloeden, Wilhelm von (1856-1931) - n. 1948 recto - Due ragazzi nudi nel giardino di Gloeden - Getty Museum - Cm 16,7x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1994 - Nudo femminile accademico - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 2031.jpg Gloeden, Wilhelm von (1856-1931) - n. 2044 recto - Due ragazzi spalla a spalla davanti a una porta - Kiermeierp. 132.jpg Gloeden, Wilhelm von (1856-1931) - n. 2067 - da - The boys of Taormina, p. 31.jpg Gloeden, Wilhelm von (1856-1931) - n. 2071.jpg Gloeden, Wilhelm von (1856-1931) - n. 2169 - Beautés siciliennes, p.40.jpg Gloeden, Wilhelm von (1856-1931) - n. 1237 - Due giovani nella campagna. Cm 12x17,5. Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 2252 - Bacchino.jpg Gloeden, Wilhelm von (1856-1931) - n. 2274 - Beautés siciliennes, p. 55 e Bloomsbury auctions Italia.jpg Gloeden, Wilhelm von (1856-1931) - n. 2412 - Rosina Buciunì con fiori, cm 22x16,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 recto - cm 23x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 verso, Deposé 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 2448.jpg Gloeden, Wilhelm von (1856-1931) - n. 2755 recto - da - Auch ich in Arkadien p. 76.jpg Gloeden, Wilhelm von (1856-1931) - n. 2471 - Paradis sicilien, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 2483 recto - Tre bambini sotto a una croce - Beautés siciliennes, p. 101.jpg Gloeden, Wilhelm von (1856-1931) - n. 2528 A - Giovane moro Sehnsucht p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 r - Tunisian boy.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 v.jpg Gloeden, Wilhelm von (1856-1931) - n. 2556 - Tunisi - Porticato moresco - Sehnsucht p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 2600.jpg Gloeden, Wilhelm von (1856-1931) - n. 2675 - Sensucht p. 77.jpg Gloeden, Wilhelm von (1856-1931) - n. 2700 - ebay.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._2717.jpg Gloeden, Wilhelm von (1856-1931) - n. 2756 - Hotel Timeo & Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 2825 - Paradis sicilien, p. 53.jpg Gloeden, Wilhelm von (1856-1931) - n. 2836 = 1336.jpg Gloeden, Wilhelm von (1856-1931) - n. 2885.jpg Gloeden, Wilhelm von (1856-1931) - n. 2936.jpg Gloeden, Wilhelm von (1856-1931) - n. 3091 B.jpg Reclining Male Nude Beside Vase - Wilhelm von Gloeden.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 B - Ragazzo con cappello in mano.jpg Gloeden, Wilhelm von (1856-1931) - n. 1015 recto - Ragazzo abruzzese - Timbrata. Cm 11x16.jpg Gloeden, Wilhelm von (1856-1931) - n. 1007 - Ragazzo e vecchio - Abruzzo, ca 1880 - Dal sito del National Geographic.jpg Gloeden, Wilhelm von (1856-1931) - n. 1685 - A naked Sicilian boy, in a rocky setting outdoors. Wellcome L0034527.jpg Gloeden, Wilhelm von (1856-1931) - n. 0349 - A Sicilian boy, posing naked by a doorway - Wellcome L0034528.jpg Gloeden, Wilhelm von (1856-1931) - n. 0524 - A draped youth and a naked woman, posing naked outdoors - Cm 16,9x22,5 - Wellcome L0034529.jpg Gloeden, Wilhelm von (1856-1931) - n. 0510 - Maria - Datata 1893.jpg|Maria, 1893 Plüschow, Wilhelm von (1852-1930) - n. ... - Youth crowned with flowers.jpg Gloeden, Wilhelm von (1856-1931) - n. 0146 recto - Three nude boys in front of a closed door - National Museum, Warsaw.jpg Gloeden, Wilhem von (1856-1931) - n. 1730 - Maria Intelisano.jpg Wilhelm von Gloeden (attr) Taormina Teatro.jpg Wilhelm von Gloeden Young male posed on cliff.jpg Wilhelm von Gloeden, Hypnos.jpg|''Hypnos'' §Gloeden. Wilhem von (1856-1931) - 1890 ca. - Autoritratto come arabo - Taschen p. 6.jpg Gloeden - LAB4.jpg Gloeden, Wilhelm von (1856-1931) - Costume siciliano.jpg|Garçon habillé en fille Gloeden, Wilhelm von (1856-1931) - n. 0089 recto - Tomba di August von Platen a Siracusa, nel 1900 - Ebay.png|Tomba di August von Platen a Siracusa nel 1900 </gallery> Outre ses nus masculins, von Gloeden a également réalisé des photographies de [[femme]]s, des [[paysage]]s, etc. 4s4qjkc8ysqno0rtdmu3hvsafvfx7ez 765974 765947 2026-05-04T16:27:41Z Geoffroi 121497 ([[c:GR|GR]]) [[c:COM:FR|File renamed]]: [[File:Gloeden, Wilhelm von (1856-1931) - n. 0611.jpg]] → [[File:Gloeden, Wilhelm von (1856-1931) - n. 0611 recto - Due ragazzi nudi sulla riva del mare, cm 17x22,5 - Beautés siciliennes, p. 71.jpg]] [[c:COM:FR#FR1|Criterion 1]] (original uploader’s request) 765974 wikitext text/x-wiki {{Ph s Personnalités}} [[Image:Wilhelm von Gloeden (c. 1891).jpg|thumb|Autoportrait (1891)]] Le baron '''Wilhelm von Gloeden''', né à Wismar (Mecklembourg) le 16 septembre 1856 et mort à Taormina le 16 février 1931, était un photographe allemand. == Biographie == Wilhelm von Gloeden est considéré comme l'un des plus grands photographes de [[nu]] masculin et aussi comme l'un des pionniers de la photographie en plein air. Après avoir étudié l'histoire de l'art à Rostock, il suivit une formation de peintre. En 1878, pour soigner sa tuberculose, il se rendit, sur le conseil de son médecin, à Taormina en Sicile. Le peintre Otto Geleng qui y vivait déjà lui avait parlé de ce lieu paradisiaque. Émerveillé par les paysages siciliens, mais surtout par la beauté sauvage et antique des jeunes paysans et pêcheurs de Taormina, von Gloeden s'initia à la photographie, aidé aussi bien par les photographes locaux que par son cousin [[Wilhelm von Plüschow]] qui vivait à Naples et qui était, lui aussi, fasciné par le charme des jeunes Italiens du sud. Von Gloeden devint rapidement célèbre pour ses clichés d'éphèbes aux poses très inspirées de l'art antique. Ses photographies de nu masculin dégagent une puissance érotique peu égalée. Il reçut vers 1900 la visite d'un autre futur grand photographe du genre, [[Rudolph Lehnert]]. Très rapidement il devint très apprécié des esthètes de son temps qui lui commandaient des clichés : les écrivains Anatole France, Gabriele D'Annunzio, Oscar Wilde, Marcel Proust, mais aussi Richard Strauss, le ''Kaiser'' Guillaume II d'Allemagne, le Konprinz, Édouard VII, le roi d'Angleterre qui popularisa le nudisme, et même le roi du Siam. Plusieurs de ses photographies furent exposées et publiées dans les plus grands magazines spécialisés, ce qui peut sembler étonnant dans le contexte homophobe de l'époque. Si de nombreuses photographies de von Gloeden exaltent le désir homosexuel, elles ont été tolérées du fait de l'alibi de l'héritage culturel grec et surtout, parce qu'aucun de ses clichés n'est pornographique. Au début de la guerre en 1914, von Gloeden décida de rentrer en Allemagne. Lorsqu'il revint à Taormina, il avait beaucoup perdu de son inspiration et un peu de son goût pour la photographie. Beaucoup de ses modèles avaient péri à la guerre et les contraintes des normes sociales étaient désormais plus dures. Il mourut en 1931 et fut enterré près de sa sœur dans le cimetière protestant de Taormina. L'un de ses fidèles modèles et ami, Pancrazio Bucini, surnommé ''Il Moro'', hérita du fonds photographique (probablement quelques 7 000 clichés). Ces documents furent saisis par les fascistes en 1933 et 1936, environ soixante pour cent fuent détruits, et ''Il Moro'' fut condamné pour détention de photographies pornographiques, avant d'être ultérieurement acquitté. Finalement, l'œuvre du Baron est reconnue comme œuvre d'art et son jeune protégé exonéré. Il parvint à récupérer environ 800 négatifs. À sa mort en 1963, ''Il Moro'' les laissa à son fils qui les vendit à un antiquaire. Depuis l'an 2000, le fonds von Gloeden se trouve au Musée Alinari de Florence. Les tirages collectionnés durant le 20{{e}} siècle par les amateurs du travail de von Gloeden sont heureusement nombreux, tout comme les [[cartes postales]] et les catalogues d'expositions. Son œuvre rencontre encore de nos jours un vif intérêt, notamment dans la communauté homosexuelle. [[Roland Barthes]] a préfacé une monographie de von Gloeden. Si aujourd'hui Taormina continue de s'enorgueillir du séjour de von Gloeden, elle n'est plus vraiment ce qu'elle était au temps du Baron Von Gloeden ni des ses héritiers tel [[Konrad Helbig]]. == Publications == * Taormina [préface de Roland Barthes] .- Pasadena, Twelvetrees Press, 1986. {{ISBN|0942642228|9780942642223}} == Bibliographie == * CANET, Nicole .- Von Gloeden, [[Vincenzo Galdi]], Von Pluschow, Poésies Arcadiennes. Paris, Éditions Thélès, 2003. {{ISBN|2-84776-212-4}}. * CANET, Nicole .- Gloedeneries caravagesques : Von Gloeden, Von Pluschow, Vincenzo Galdi, nus masculins .- Paris, Au Bonheur du Jour, 2005. {{ISBN|2-9523322-1-5}} * CANET, Nicole .- Wilhem Von Gloeden, [[Photographie/Personnalités/P/Wilhelm von Plüschow|Guglielmo Pluschow]], [[Vincenzo Galdi]], Paradis Siciliens, Paysages, Portraits et Nus 1890 19052008. {{ISBN|2-9523322-5-8}} * FALZONE BARBARO, MIRAGLIA, Marina et MUSSA, Italo [avec une note de Goffredo Parise] .- Le fotografie di Von Gloeden .- Milan, Longansesi, 1980, 157 p. [diverses éditions de 1996 à 2000] * LEMAGNY, Jean-Claude .- Taormina, début de siècle .- Paris, Chêne, 1975, 105 p. {{ISBN|2851080423|9782851080424}} * PEYREFITTE, Roger .- Wilhelm von Gloeden .- Paris, Éditions Textes Gais, juillet 2008, biographie et cahier de 50 nus masculins. {{ISBN|2914679300|978-2914679305}} * Wilhelm von Gloeden, Wilhelm von Pluschow, Vincenzo Galdi, Italienische Jûnglings-photographien um 1900 .- Berlin, Janssen Verlag, 1991, {{ISBN|3-925443-11-8}} * Wilhelm von Gloeden, Wilhelm von Plüschow, Vincenzo Galdi, [http://www.aubonheurdujour.net/Beautes_Siciliennes.html Beautés Siciliennes], Éditions Nicole Canet, 2014 {{ISBN|978-2-9532351-7-3}}. == Voir aussi == * Biographie complète et galerie photo : http://vongloedengayhistory.free.fr/index.html * Biographie complète et galerie photo : http://www.aubonheurdujour.net/Gloeden.html {{T|[[Commons:Category:Wilhelm von Gloeden|voir d'autres photographies de von Gloeden sur Wikimedia Commons]]}} {{T|[[Commons:Catalogue of Wilhelm von Gloeden's pictures|voir le catalogue des œuvres de Wilhelm von Gloeden sur Wikimedia Commons]]}} == Galerie de photographies == <gallery widths="240px" heights="240px"> Gloeden, Wilhelm von (1856-1931) - n. 0000 - Pescatore.jpg Gloeden, Wilhelm von (1856-1931) - n. 0003 - Beauté siciliennes, p. 111.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0003 - Siesta greca.jpg Gloeden, Wilhelm von (1856-1931) - n. 0004r - Frate e ciabattino - 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 0009 - Bimbo - Galerie Bassenge - da - Beautés siciliennes, p. 117.jpg Gloeden, Wilhelm von (1856-1931) - n. 0011 - Madonna.jpg Gloeden, Wilhelm von (1856-1931) - n. 0021 - Da Amore e arte.jpg Gloeden, Wilhelm von (1856-1931) - n. 0022 - Serenata siciliana - Inexhaustible Italy, National Geographic 06-12 1916.jpg Gloeden, Wilhelm von (1856-1931) - n. 0033 B - Beauté siciliennes, p. 102 - Timbro W. von Gloeden Napoli (2).jpg Gloeden, Wilhelm von (1856-1931) - n. G 0027 - Il teatro greco di Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 0036 - da - Sicilia mitica Arcadia - p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0037B - Taormina - Teatro greco - Beautés siciliennes, p. 121 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0040, Beautés siciliennes, p. 33.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._0045_-_Terra_del_Fuoco.jpg Gloeden, Wilhelm von (1856-1931) - n. 0050 B - Mandorli - recto.tif Gloeden, Wilhelm von (1856-1931) - n. 0053 - Beautés siciliennes, p. 97.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 - Capri - Bambine - Galerie Bassenge - Beautés siciliennes, p. 113.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - recto - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - verso - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057 - Due uomini nudi seduti su un masso.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057bis - Due uomini nudi di spalle - Cm 17x22,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 061 (deposé 1902) - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0067 - Pescatore - Beautés siciliennes, p. 95.jpg Gloeden, Wilhelm von (1856-1931) - n. 0063 - Da - Beautés siciliennes p. 36.jpg Gloeden, Wilhelm von (1856-1931) - n. 0064 - La confessione.jpg Gloeden, Wilhelm von (1856-1931) - n. 0065 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 0069 B & Crupi - Isola Bella e Capo Sant'Andrea - Perna p. 10.jpg Gloeden, Wilhelm von (1856-1931) - n. 0077 B - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0081 grande formato.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0088 - (Metropolitan museum).jpg Gloeden, Wilhelm von (1856-1931) - n. 0090 - Tomba di August von Platen a Siracusa.jpg|Tomba di August von Platen a Siracusa nel 1900 Gloeden, Wilhelm von (1856-1931) - n. 0099 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 0365 - Due ragazzi in giardino - Cm 18x22.jpg Gloeden, Wilhelm von (1856-1931) - n. 0110 B - Taormina - Teatro Greco con autoritratto - Beautés siciliennes, p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 0121.jpg Gloeden, Wilhelm von (1856-1931) - n. 0123 - Ragazzo abruzzese nudo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0125 - Posillipo - Timbro e data 8 marzo 1899. Perna, p. 94.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0126 - Drout auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0134 recto - Chiostro di Morreale a Palermo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0139 - Nerone. Da Perna, p. 20 - Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 0142 - Nerone Da Auch ich in Akadien, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 151 B - Taormina - Mandorli in fiore - Sehnsucht, p.68.jpg Gloeden, Wilhelm von (1856-1931) - n. 0152 - Debutdusiècle p. 29 & Leslie ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0174 - Twelwetrees p. 86 & Auch ich in Arkadien p. 125.jpg Gloeden, Wilhelm von (1856-1931) - n. 0185 - Beautés siciliennes, p. 114 - Campionario.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0210 recto - Hagar - Aste La Rosa.jpg Gloeden, Wilhelm von (1856-1931) - n. 0191, cm 18x24 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0195 - Perna p. 33.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0203 - Giovane arabo - Taschen p.79.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0208. Sigla BC e data 1909.jpg Gloeden, Wilhelm von (1856-1931) - n. 0222 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0224 - Peppino - Gallo, p. 17.jpg Gloeden, Wilhelm von (1856-1931) - n. 0225 - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - da - Amore e arte, p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - Les musiciens,1897 Tampon encré Gloeden au dos et date 1897.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 r - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 v - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0243 - 1899 - Da ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0246 - 1914 - Paesaggio taorminese.jpg Gloeden, Wilhelm von (1856-1931) - n. 0249 - Debutdesiècle p. 73.jpg Gloeden, Wilhelm von (1856-1931) - n. 0255 B - Sito Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1258 - Amore e arte, p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0263 - Caino, ca. 1902.jpg|''Caino'' Gloeden, Wilhelm von (1856-1931) - n. G 0263 - Caino.jpg Gloeden, Wilhelm von (1856-1931) - n. 0283.jpg Gloeden, Wilhelm von (1856-1931) - n. 0287 recto - Due giovani drappeggiati su un sentiero del Monte Ziretto - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0299 - Janssen, p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0307 - Janssen p. 23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 r - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 verso - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0313 recto - Peppino Scavo vestito alla greca - ex collection Levy - Cm 16x23 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0317 B recto - Peppino Scavo vestito alla greca - Cm 17,2x23,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 0347 recto - Timbrata a secco Gaetano Pedo, Roma.jpg Gloeden, Wilhelm von (1856-1931) - n. 0352 - Beautés siciliennes, p. 57b.jpg Gloeden, Wilhelm von (1856-1931) - n. 0354 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0358 - da - Auch ich in Arkadien, p. 167.jpg Gloeden, Wilhelm von (1856-1931) - n. 0396 B recto - Zannier, p. 141.jpg Gloeden, Wilhelm von (1856-1931) - n. 0339 - Gloedeneries caravagesques, 2005, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 0401 recto - Due ragazzi accanto a due barche a una nassa - Ebay, cm 13x18.png Gloeden, Wilhelm von (1856-1931) - n. 0405 - Piazza San Domenico - Archivio Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 0410 - Photogravure from Nus, printed by Buchverlag, 1926.jpg Gloeden, Wilhelm von (1856-1931) - n. 0114 recto - Giuseppe Capasso nel Teatro greco. Cm 11,5x15,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 0425 - da Et in Arcadia, p. 90.jpg Gloeden, Wilhelm von (1856-1931) - n. 0434 - Pietro di Napoli - Janssen p. 9.jpg Gloeden, Wilhelm von (1856-1931) - n. 0449 - Beautés siciliennes, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0445 recto - Due ragazzi nudi, con anfora e canna di bambù, accanto a un muro - Zannier p. 120, cm 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 0457 - Twelwetrees p. 88.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 recto - cm 17x23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 verso - Deponiert Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0474 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0437 recto - Due ragazzi nudi abbracciati.jpg Gloeden, Wilhelm von (1856-1931) - n. 1149 - Caputo, p. 81.jpg Gloeden, Wilhelm von (1856-1931) - n. 0545 - 24c - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0549 - Paradis sicilien, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0550 B - recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0557 B recto - Rimbaud 2 p. 5 & Perna p. 62.jpg Gloeden, Wilhelm von (1856-1931) - n. 0574 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0579.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 recto - Beautés siciliennes, p. 87.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 verso - Beautés siciliennes, p. 87 - Ristampato da Buciunì.jpg Gloeden, Wilhelm von (1856-1931) - n. 0611 recto - Due ragazzi nudi sulla riva del mare, cm 17x22,5 - Beautés siciliennes, p. 71.jpg Gloeden, Wilhelm von (1856-1931) - n. 0621 recto - Ex Texbraun Collection, Galerie David Guiraud, Paris.jpg Gloeden, Wilhelm von (1856-1931) - n. 0645 - 4-2-1899 - Da - Barthes p. 46 & Auch ich in Arkadien p. 89.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 r - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 v - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0677.jpg Gloeden, Wilhelm von (1856-1931) - n. 0709 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 0717 - Siracusa latomie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0735 recto - Carlo Siligato e Pietro Mazza.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0127 recto - Gloedeneries p. 14.jpg Plüschow, Wilhelm von (1852-1930) - n. 0767 recto - Ragazzino nudo di spalle - Paradis sicilien, p. 43 - Napoli, Cm 11,5 x16,8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0769 - Twelvetrees p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0776 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0799 - Beautés siciliennes, p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0803 - da - Et in Arcadia ego, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0804 - Levante p. 47 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0831 recto - Pietro con un ramoscello sulle spalle, davanti a Mazzarò - Bonham auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. 0842 - Carlotta.jpg Gloeden, Wilhelm von (1856-1931) - n. 0845 - Gallo, p 8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0849 recto - Ragazzo drappeggiato, vestito da donna - Bassenge - Cm 17x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 0858 - Datato 12-9-1910 - Deposé 1903 - Cm 16,9x22, Turner p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0870 - Carlotta - 1890 ca. - Mussa, Italo - Wilhelm von Gloeden, ed. Malambri, 1980 p.....jpg Gloeden, Wilhelm von (1856-1931) - n. 0892 - Bimbo con stampella - Deponirt 1 Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0917 - Minerva auctions déc 2012.jpg Gloeden, Wilhelm von (1856-1931) - n. 0922 - Zannier, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0937 - Piccolo imperatore - da - Gallo, p.14.jpg Gloeden, Wilhelm von (1856-1931) - n. 0149 - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0996 - Twelvetrees p. 49.jpg Gloeden, Wilhelm von (1856-1931) - n. 1005.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 recto - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 verso - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1038 recto - Due uomini davanti a porta - Collezione privata.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._1039_-_da_-_Amore_e_arte,_p._92.jpg Gloeden, Wilhelm von (1856-1931) - n. 1050 recto - Quattro uomini nudi al mare - Schwulen Archiv Zürich - Cm 24x18.jpg Gloeden, Wilhelm von (1856-1931) - n. 1051 - Galerie au bonheur du jour ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1077 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1080 - Paradis sicilien, p. 51.jpg Gloeden, Wilhem von (1856-1931) - n. 1094 - Nudo maschile con cane.jpg Gloeden, Wilhelm von (1856-1931) - n. 1104 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1115 - Twelvetrees p. 40 & Perna p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 1117 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 - da - Auch ich in Arkadien, p. 165.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1160 (dated 1898).jpg Gloeden, Wilhelm von (1856-1931) - n. 1164.jpg Gloeden, Wilhelm von (1856-1931) - n. 1192 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1199 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1200 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1201 - Da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1211 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1228 - da - Auch ich in Arkadien, p. 178 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 1246 - Budapest library e Perna, p. 78.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 r - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 v - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1254 recto - Pasquale a torso nudo appoggiato a una roccia - Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1259 - 1899 - Taschen p.89.jpg Gloeden, Wilhelm von (1856-1931) - n. 1290 - Barthes p. 50 & Auch ich in Arkadien p. 169.jpg Gloeden, Wilhelm von (1856-1931) - n. 1310 - Timbrata - Beautés siciliennes, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 1348 - Napoli - Taschen p.51 e Pohlmann p. 140.jpg Gloeden, Wilhelm von (1856-1931) - n. 1355 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1360 - Barthes p. 52 e Auch ich in Arkadien, p. 106.jpg Gloeden, Wilhelm von (1856-1931) - n. 1365.jpg Gloeden, Wilhelm von (1856-1931) - n. 1368 - Deposé Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1369 - deponiert 20 oct. 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1398.jpg Gloeden, Wilhelm von (1856-1931) - n. 1403 recto - Donna in costume siciliano - Cm 16,9x22,4.jpg Gloeden, Wilhelm von (1856-1931) - n. 1459.jpg Gloeden, Wilhelm von (1856-1931) - n. 1519 recto - "Sguardo alla preda" - Déposé 17 Lug 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1526 - cm 22x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 1531 - Deposé 2 Jan 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1541 - deponirt 17 febr 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1590 - Adolescente laureato - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1600 dated 16 juin 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1610.jpg Gloeden, Wilhelm von (1856-1931) - n. 1628 - da - Auch ich in Arkadien, p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 1641 - Nudo accademico maschile di fronte - Getty Museum - 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 1703 - G. Pedo, 130 Via Sistina Roma. Deponirt 1 Aug. 1900 (Galerie Bassenge).jpg Gloeden, Wilhelm von (1856-1931) - n. 1721.jpg Gloeden, Wilhelm von (1856-1931) - n. 1731 - Maria Intelisano - 1905 ca. - da - Sicilia mitica arcadia, p. 64.jpg Gloeden, Wilhelm von (1856-1931) - n. 1733 recto - Profilo vago (Maria Intelisano).jpg Gloeden, Wilhem von (1856-1931) - n. 1735.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 recto - Ritratto di Giacomo Lanfranchi travestito da ragazza - Ebay - Cm 16,8x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 (colorata a mano).jpg Gloeden, Wilhelm von (1856-1931) - n. 1741 recto - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1744 - Hypnos.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 - (Hypnos) - Perna, p. 25 & Leslie Lohman faundation.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 A recto - Russel p. 73 e Beautés siciliennes p. 47.jpg Gloeden, Wilhelm von (1856-1931) - n. 1836 recto - Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1868 recto - Due adolescenti nudi affiancati contro un muro - Auctionsfr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1870 - da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 2095 recto - Autoritratto come Nazareno - Sehnsucht, p. 27.jpg Gloeden, Wilhelm von (1856-1931) - n. 1948 recto - Due ragazzi nudi nel giardino di Gloeden - Getty Museum - Cm 16,7x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1994 - Nudo femminile accademico - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 2031.jpg Gloeden, Wilhelm von (1856-1931) - n. 2044 recto - Due ragazzi spalla a spalla davanti a una porta - Kiermeierp. 132.jpg Gloeden, Wilhelm von (1856-1931) - n. 2067 - da - The boys of Taormina, p. 31.jpg Gloeden, Wilhelm von (1856-1931) - n. 2071.jpg Gloeden, Wilhelm von (1856-1931) - n. 2169 - Beautés siciliennes, p.40.jpg Gloeden, Wilhelm von (1856-1931) - n. 1237 - Due giovani nella campagna. Cm 12x17,5. Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 2252 - Bacchino.jpg Gloeden, Wilhelm von (1856-1931) - n. 2274 - Beautés siciliennes, p. 55 e Bloomsbury auctions Italia.jpg Gloeden, Wilhelm von (1856-1931) - n. 2412 - Rosina Buciunì con fiori, cm 22x16,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 recto - cm 23x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 verso, Deposé 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 2448.jpg Gloeden, Wilhelm von (1856-1931) - n. 2755 recto - da - Auch ich in Arkadien p. 76.jpg Gloeden, Wilhelm von (1856-1931) - n. 2471 - Paradis sicilien, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 2483 recto - Tre bambini sotto a una croce - Beautés siciliennes, p. 101.jpg Gloeden, Wilhelm von (1856-1931) - n. 2528 A - Giovane moro Sehnsucht p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 r - Tunisian boy.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 v.jpg Gloeden, Wilhelm von (1856-1931) - n. 2556 - Tunisi - Porticato moresco - Sehnsucht p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 2600.jpg Gloeden, Wilhelm von (1856-1931) - n. 2675 - Sensucht p. 77.jpg Gloeden, Wilhelm von (1856-1931) - n. 2700 - ebay.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._2717.jpg Gloeden, Wilhelm von (1856-1931) - n. 2756 - Hotel Timeo & Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 2825 - Paradis sicilien, p. 53.jpg Gloeden, Wilhelm von (1856-1931) - n. 2836 = 1336.jpg Gloeden, Wilhelm von (1856-1931) - n. 2885.jpg Gloeden, Wilhelm von (1856-1931) - n. 2936.jpg Gloeden, Wilhelm von (1856-1931) - n. 3091 B.jpg Reclining Male Nude Beside Vase - Wilhelm von Gloeden.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 B - Ragazzo con cappello in mano.jpg Gloeden, Wilhelm von (1856-1931) - n. 1015 recto - Ragazzo abruzzese - Timbrata. Cm 11x16.jpg Gloeden, Wilhelm von (1856-1931) - n. 1007 - Ragazzo e vecchio - Abruzzo, ca 1880 - Dal sito del National Geographic.jpg Gloeden, Wilhelm von (1856-1931) - n. 1685 - A naked Sicilian boy, in a rocky setting outdoors. Wellcome L0034527.jpg Gloeden, Wilhelm von (1856-1931) - n. 0349 - A Sicilian boy, posing naked by a doorway - Wellcome L0034528.jpg Gloeden, Wilhelm von (1856-1931) - n. 0524 - A draped youth and a naked woman, posing naked outdoors - Cm 16,9x22,5 - Wellcome L0034529.jpg Gloeden, Wilhelm von (1856-1931) - n. 0510 - Maria - Datata 1893.jpg|Maria, 1893 Plüschow, Wilhelm von (1852-1930) - n. ... - Youth crowned with flowers.jpg Gloeden, Wilhelm von (1856-1931) - n. 0146 recto - Three nude boys in front of a closed door - National Museum, Warsaw.jpg Gloeden, Wilhem von (1856-1931) - n. 1730 - Maria Intelisano.jpg Wilhelm von Gloeden (attr) Taormina Teatro.jpg Wilhelm von Gloeden Young male posed on cliff.jpg Wilhelm von Gloeden, Hypnos.jpg|''Hypnos'' §Gloeden. Wilhem von (1856-1931) - 1890 ca. - Autoritratto come arabo - Taschen p. 6.jpg Gloeden - LAB4.jpg Gloeden, Wilhelm von (1856-1931) - Costume siciliano.jpg|Garçon habillé en fille Gloeden, Wilhelm von (1856-1931) - n. 0089 recto - Tomba di August von Platen a Siracusa, nel 1900 - Ebay.png|Tomba di August von Platen a Siracusa nel 1900 </gallery> Outre ses nus masculins, von Gloeden a également réalisé des photographies de [[femme]]s, des [[paysage]]s, etc. jx6srjhcurof2m8hc5q9ndwv8dvibsy 765976 765974 2026-05-04T16:32:57Z Geoffroi 121497 ([[c:GR|GR]]) [[c:COM:FR|File renamed]]: [[File:Gloeden, Wilhelm von (1856-1931) - n. 2885.jpg]] → [[File:Gloeden, Wilhelm von (1856-1931) - n. 2885 recto - Due giovani sulla scala della casa di Gloeden - L'arte di Gloeden, p. 125.jpg]] [[c:COM:FR#FR1|Criterion 1]] (original uploader’s request) 765976 wikitext text/x-wiki {{Ph s Personnalités}} [[Image:Wilhelm von Gloeden (c. 1891).jpg|thumb|Autoportrait (1891)]] Le baron '''Wilhelm von Gloeden''', né à Wismar (Mecklembourg) le 16 septembre 1856 et mort à Taormina le 16 février 1931, était un photographe allemand. == Biographie == Wilhelm von Gloeden est considéré comme l'un des plus grands photographes de [[nu]] masculin et aussi comme l'un des pionniers de la photographie en plein air. Après avoir étudié l'histoire de l'art à Rostock, il suivit une formation de peintre. En 1878, pour soigner sa tuberculose, il se rendit, sur le conseil de son médecin, à Taormina en Sicile. Le peintre Otto Geleng qui y vivait déjà lui avait parlé de ce lieu paradisiaque. Émerveillé par les paysages siciliens, mais surtout par la beauté sauvage et antique des jeunes paysans et pêcheurs de Taormina, von Gloeden s'initia à la photographie, aidé aussi bien par les photographes locaux que par son cousin [[Wilhelm von Plüschow]] qui vivait à Naples et qui était, lui aussi, fasciné par le charme des jeunes Italiens du sud. Von Gloeden devint rapidement célèbre pour ses clichés d'éphèbes aux poses très inspirées de l'art antique. Ses photographies de nu masculin dégagent une puissance érotique peu égalée. Il reçut vers 1900 la visite d'un autre futur grand photographe du genre, [[Rudolph Lehnert]]. Très rapidement il devint très apprécié des esthètes de son temps qui lui commandaient des clichés : les écrivains Anatole France, Gabriele D'Annunzio, Oscar Wilde, Marcel Proust, mais aussi Richard Strauss, le ''Kaiser'' Guillaume II d'Allemagne, le Konprinz, Édouard VII, le roi d'Angleterre qui popularisa le nudisme, et même le roi du Siam. Plusieurs de ses photographies furent exposées et publiées dans les plus grands magazines spécialisés, ce qui peut sembler étonnant dans le contexte homophobe de l'époque. Si de nombreuses photographies de von Gloeden exaltent le désir homosexuel, elles ont été tolérées du fait de l'alibi de l'héritage culturel grec et surtout, parce qu'aucun de ses clichés n'est pornographique. Au début de la guerre en 1914, von Gloeden décida de rentrer en Allemagne. Lorsqu'il revint à Taormina, il avait beaucoup perdu de son inspiration et un peu de son goût pour la photographie. Beaucoup de ses modèles avaient péri à la guerre et les contraintes des normes sociales étaient désormais plus dures. Il mourut en 1931 et fut enterré près de sa sœur dans le cimetière protestant de Taormina. L'un de ses fidèles modèles et ami, Pancrazio Bucini, surnommé ''Il Moro'', hérita du fonds photographique (probablement quelques 7 000 clichés). Ces documents furent saisis par les fascistes en 1933 et 1936, environ soixante pour cent fuent détruits, et ''Il Moro'' fut condamné pour détention de photographies pornographiques, avant d'être ultérieurement acquitté. Finalement, l'œuvre du Baron est reconnue comme œuvre d'art et son jeune protégé exonéré. Il parvint à récupérer environ 800 négatifs. À sa mort en 1963, ''Il Moro'' les laissa à son fils qui les vendit à un antiquaire. Depuis l'an 2000, le fonds von Gloeden se trouve au Musée Alinari de Florence. Les tirages collectionnés durant le 20{{e}} siècle par les amateurs du travail de von Gloeden sont heureusement nombreux, tout comme les [[cartes postales]] et les catalogues d'expositions. Son œuvre rencontre encore de nos jours un vif intérêt, notamment dans la communauté homosexuelle. [[Roland Barthes]] a préfacé une monographie de von Gloeden. Si aujourd'hui Taormina continue de s'enorgueillir du séjour de von Gloeden, elle n'est plus vraiment ce qu'elle était au temps du Baron Von Gloeden ni des ses héritiers tel [[Konrad Helbig]]. == Publications == * Taormina [préface de Roland Barthes] .- Pasadena, Twelvetrees Press, 1986. {{ISBN|0942642228|9780942642223}} == Bibliographie == * CANET, Nicole .- Von Gloeden, [[Vincenzo Galdi]], Von Pluschow, Poésies Arcadiennes. Paris, Éditions Thélès, 2003. {{ISBN|2-84776-212-4}}. * CANET, Nicole .- Gloedeneries caravagesques : Von Gloeden, Von Pluschow, Vincenzo Galdi, nus masculins .- Paris, Au Bonheur du Jour, 2005. {{ISBN|2-9523322-1-5}} * CANET, Nicole .- Wilhem Von Gloeden, [[Photographie/Personnalités/P/Wilhelm von Plüschow|Guglielmo Pluschow]], [[Vincenzo Galdi]], Paradis Siciliens, Paysages, Portraits et Nus 1890 19052008. {{ISBN|2-9523322-5-8}} * FALZONE BARBARO, MIRAGLIA, Marina et MUSSA, Italo [avec une note de Goffredo Parise] .- Le fotografie di Von Gloeden .- Milan, Longansesi, 1980, 157 p. [diverses éditions de 1996 à 2000] * LEMAGNY, Jean-Claude .- Taormina, début de siècle .- Paris, Chêne, 1975, 105 p. {{ISBN|2851080423|9782851080424}} * PEYREFITTE, Roger .- Wilhelm von Gloeden .- Paris, Éditions Textes Gais, juillet 2008, biographie et cahier de 50 nus masculins. {{ISBN|2914679300|978-2914679305}} * Wilhelm von Gloeden, Wilhelm von Pluschow, Vincenzo Galdi, Italienische Jûnglings-photographien um 1900 .- Berlin, Janssen Verlag, 1991, {{ISBN|3-925443-11-8}} * Wilhelm von Gloeden, Wilhelm von Plüschow, Vincenzo Galdi, [http://www.aubonheurdujour.net/Beautes_Siciliennes.html Beautés Siciliennes], Éditions Nicole Canet, 2014 {{ISBN|978-2-9532351-7-3}}. == Voir aussi == * Biographie complète et galerie photo : http://vongloedengayhistory.free.fr/index.html * Biographie complète et galerie photo : http://www.aubonheurdujour.net/Gloeden.html {{T|[[Commons:Category:Wilhelm von Gloeden|voir d'autres photographies de von Gloeden sur Wikimedia Commons]]}} {{T|[[Commons:Catalogue of Wilhelm von Gloeden's pictures|voir le catalogue des œuvres de Wilhelm von Gloeden sur Wikimedia Commons]]}} == Galerie de photographies == <gallery widths="240px" heights="240px"> Gloeden, Wilhelm von (1856-1931) - n. 0000 - Pescatore.jpg Gloeden, Wilhelm von (1856-1931) - n. 0003 - Beauté siciliennes, p. 111.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0003 - Siesta greca.jpg Gloeden, Wilhelm von (1856-1931) - n. 0004r - Frate e ciabattino - 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 0009 - Bimbo - Galerie Bassenge - da - Beautés siciliennes, p. 117.jpg Gloeden, Wilhelm von (1856-1931) - n. 0011 - Madonna.jpg Gloeden, Wilhelm von (1856-1931) - n. 0021 - Da Amore e arte.jpg Gloeden, Wilhelm von (1856-1931) - n. 0022 - Serenata siciliana - Inexhaustible Italy, National Geographic 06-12 1916.jpg Gloeden, Wilhelm von (1856-1931) - n. 0033 B - Beauté siciliennes, p. 102 - Timbro W. von Gloeden Napoli (2).jpg Gloeden, Wilhelm von (1856-1931) - n. G 0027 - Il teatro greco di Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 0036 - da - Sicilia mitica Arcadia - p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0037B - Taormina - Teatro greco - Beautés siciliennes, p. 121 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0040, Beautés siciliennes, p. 33.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._0045_-_Terra_del_Fuoco.jpg Gloeden, Wilhelm von (1856-1931) - n. 0050 B - Mandorli - recto.tif Gloeden, Wilhelm von (1856-1931) - n. 0053 - Beautés siciliennes, p. 97.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 - Capri - Bambine - Galerie Bassenge - Beautés siciliennes, p. 113.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - recto - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - verso - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057 - Due uomini nudi seduti su un masso.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057bis - Due uomini nudi di spalle - Cm 17x22,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 061 (deposé 1902) - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0067 - Pescatore - Beautés siciliennes, p. 95.jpg Gloeden, Wilhelm von (1856-1931) - n. 0063 - Da - Beautés siciliennes p. 36.jpg Gloeden, Wilhelm von (1856-1931) - n. 0064 - La confessione.jpg Gloeden, Wilhelm von (1856-1931) - n. 0065 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 0069 B & Crupi - Isola Bella e Capo Sant'Andrea - Perna p. 10.jpg Gloeden, Wilhelm von (1856-1931) - n. 0077 B - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0081 grande formato.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0088 - (Metropolitan museum).jpg Gloeden, Wilhelm von (1856-1931) - n. 0090 - Tomba di August von Platen a Siracusa.jpg|Tomba di August von Platen a Siracusa nel 1900 Gloeden, Wilhelm von (1856-1931) - n. 0099 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 0365 - Due ragazzi in giardino - Cm 18x22.jpg Gloeden, Wilhelm von (1856-1931) - n. 0110 B - Taormina - Teatro Greco con autoritratto - Beautés siciliennes, p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 0121.jpg Gloeden, Wilhelm von (1856-1931) - n. 0123 - Ragazzo abruzzese nudo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0125 - Posillipo - Timbro e data 8 marzo 1899. Perna, p. 94.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0126 - Drout auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0134 recto - Chiostro di Morreale a Palermo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0139 - Nerone. Da Perna, p. 20 - Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 0142 - Nerone Da Auch ich in Akadien, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 151 B - Taormina - Mandorli in fiore - Sehnsucht, p.68.jpg Gloeden, Wilhelm von (1856-1931) - n. 0152 - Debutdusiècle p. 29 & Leslie ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0174 - Twelwetrees p. 86 & Auch ich in Arkadien p. 125.jpg Gloeden, Wilhelm von (1856-1931) - n. 0185 - Beautés siciliennes, p. 114 - Campionario.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0210 recto - Hagar - Aste La Rosa.jpg Gloeden, Wilhelm von (1856-1931) - n. 0191, cm 18x24 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0195 - Perna p. 33.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0203 - Giovane arabo - Taschen p.79.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0208. Sigla BC e data 1909.jpg Gloeden, Wilhelm von (1856-1931) - n. 0222 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0224 - Peppino - Gallo, p. 17.jpg Gloeden, Wilhelm von (1856-1931) - n. 0225 - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - da - Amore e arte, p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - Les musiciens,1897 Tampon encré Gloeden au dos et date 1897.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 r - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 v - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0243 - 1899 - Da ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0246 - 1914 - Paesaggio taorminese.jpg Gloeden, Wilhelm von (1856-1931) - n. 0249 - Debutdesiècle p. 73.jpg Gloeden, Wilhelm von (1856-1931) - n. 0255 B - Sito Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1258 - Amore e arte, p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0263 - Caino, ca. 1902.jpg|''Caino'' Gloeden, Wilhelm von (1856-1931) - n. G 0263 - Caino.jpg Gloeden, Wilhelm von (1856-1931) - n. 0283.jpg Gloeden, Wilhelm von (1856-1931) - n. 0287 recto - Due giovani drappeggiati su un sentiero del Monte Ziretto - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0299 - Janssen, p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0307 - Janssen p. 23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 r - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 verso - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0313 recto - Peppino Scavo vestito alla greca - ex collection Levy - Cm 16x23 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0317 B recto - Peppino Scavo vestito alla greca - Cm 17,2x23,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 0347 recto - Timbrata a secco Gaetano Pedo, Roma.jpg Gloeden, Wilhelm von (1856-1931) - n. 0352 - Beautés siciliennes, p. 57b.jpg Gloeden, Wilhelm von (1856-1931) - n. 0354 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0358 - da - Auch ich in Arkadien, p. 167.jpg Gloeden, Wilhelm von (1856-1931) - n. 0396 B recto - Zannier, p. 141.jpg Gloeden, Wilhelm von (1856-1931) - n. 0339 - Gloedeneries caravagesques, 2005, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 0401 recto - Due ragazzi accanto a due barche a una nassa - Ebay, cm 13x18.png Gloeden, Wilhelm von (1856-1931) - n. 0405 - Piazza San Domenico - Archivio Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 0410 - Photogravure from Nus, printed by Buchverlag, 1926.jpg Gloeden, Wilhelm von (1856-1931) - n. 0114 recto - Giuseppe Capasso nel Teatro greco. Cm 11,5x15,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 0425 - da Et in Arcadia, p. 90.jpg Gloeden, Wilhelm von (1856-1931) - n. 0434 - Pietro di Napoli - Janssen p. 9.jpg Gloeden, Wilhelm von (1856-1931) - n. 0449 - Beautés siciliennes, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0445 recto - Due ragazzi nudi, con anfora e canna di bambù, accanto a un muro - Zannier p. 120, cm 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 0457 - Twelwetrees p. 88.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 recto - cm 17x23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 verso - Deponiert Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0474 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0437 recto - Due ragazzi nudi abbracciati.jpg Gloeden, Wilhelm von (1856-1931) - n. 1149 - Caputo, p. 81.jpg Gloeden, Wilhelm von (1856-1931) - n. 0545 - 24c - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0549 - Paradis sicilien, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0550 B - recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0557 B recto - Rimbaud 2 p. 5 & Perna p. 62.jpg Gloeden, Wilhelm von (1856-1931) - n. 0574 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0579.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 recto - Beautés siciliennes, p. 87.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 verso - Beautés siciliennes, p. 87 - Ristampato da Buciunì.jpg Gloeden, Wilhelm von (1856-1931) - n. 0611 recto - Due ragazzi nudi sulla riva del mare, cm 17x22,5 - Beautés siciliennes, p. 71.jpg Gloeden, Wilhelm von (1856-1931) - n. 0621 recto - Ex Texbraun Collection, Galerie David Guiraud, Paris.jpg Gloeden, Wilhelm von (1856-1931) - n. 0645 - 4-2-1899 - Da - Barthes p. 46 & Auch ich in Arkadien p. 89.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 r - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 v - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0677.jpg Gloeden, Wilhelm von (1856-1931) - n. 0709 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 0717 - Siracusa latomie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0735 recto - Carlo Siligato e Pietro Mazza.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0127 recto - Gloedeneries p. 14.jpg Plüschow, Wilhelm von (1852-1930) - n. 0767 recto - Ragazzino nudo di spalle - Paradis sicilien, p. 43 - Napoli, Cm 11,5 x16,8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0769 - Twelvetrees p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0776 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0799 - Beautés siciliennes, p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0803 - da - Et in Arcadia ego, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0804 - Levante p. 47 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0831 recto - Pietro con un ramoscello sulle spalle, davanti a Mazzarò - Bonham auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. 0842 - Carlotta.jpg Gloeden, Wilhelm von (1856-1931) - n. 0845 - Gallo, p 8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0849 recto - Ragazzo drappeggiato, vestito da donna - Bassenge - Cm 17x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 0858 - Datato 12-9-1910 - Deposé 1903 - Cm 16,9x22, Turner p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0870 - Carlotta - 1890 ca. - Mussa, Italo - Wilhelm von Gloeden, ed. Malambri, 1980 p.....jpg Gloeden, Wilhelm von (1856-1931) - n. 0892 - Bimbo con stampella - Deponirt 1 Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0917 - Minerva auctions déc 2012.jpg Gloeden, Wilhelm von (1856-1931) - n. 0922 - Zannier, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0937 - Piccolo imperatore - da - Gallo, p.14.jpg Gloeden, Wilhelm von (1856-1931) - n. 0149 - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0996 - Twelvetrees p. 49.jpg Gloeden, Wilhelm von (1856-1931) - n. 1005.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 recto - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 verso - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1038 recto - Due uomini davanti a porta - Collezione privata.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._1039_-_da_-_Amore_e_arte,_p._92.jpg Gloeden, Wilhelm von (1856-1931) - n. 1050 recto - Quattro uomini nudi al mare - Schwulen Archiv Zürich - Cm 24x18.jpg Gloeden, Wilhelm von (1856-1931) - n. 1051 - Galerie au bonheur du jour ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1077 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1080 - Paradis sicilien, p. 51.jpg Gloeden, Wilhem von (1856-1931) - n. 1094 - Nudo maschile con cane.jpg Gloeden, Wilhelm von (1856-1931) - n. 1104 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1115 - Twelvetrees p. 40 & Perna p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 1117 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 - da - Auch ich in Arkadien, p. 165.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1160 (dated 1898).jpg Gloeden, Wilhelm von (1856-1931) - n. 1164.jpg Gloeden, Wilhelm von (1856-1931) - n. 1192 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1199 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1200 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1201 - Da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1211 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1228 - da - Auch ich in Arkadien, p. 178 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 1246 - Budapest library e Perna, p. 78.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 r - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 v - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1254 recto - Pasquale a torso nudo appoggiato a una roccia - Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1259 - 1899 - Taschen p.89.jpg Gloeden, Wilhelm von (1856-1931) - n. 1290 - Barthes p. 50 & Auch ich in Arkadien p. 169.jpg Gloeden, Wilhelm von (1856-1931) - n. 1310 - Timbrata - Beautés siciliennes, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 1348 - Napoli - Taschen p.51 e Pohlmann p. 140.jpg Gloeden, Wilhelm von (1856-1931) - n. 1355 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1360 - Barthes p. 52 e Auch ich in Arkadien, p. 106.jpg Gloeden, Wilhelm von (1856-1931) - n. 1365.jpg Gloeden, Wilhelm von (1856-1931) - n. 1368 - Deposé Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1369 - deponiert 20 oct. 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1398.jpg Gloeden, Wilhelm von (1856-1931) - n. 1403 recto - Donna in costume siciliano - Cm 16,9x22,4.jpg Gloeden, Wilhelm von (1856-1931) - n. 1459.jpg Gloeden, Wilhelm von (1856-1931) - n. 1519 recto - "Sguardo alla preda" - Déposé 17 Lug 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1526 - cm 22x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 1531 - Deposé 2 Jan 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1541 - deponirt 17 febr 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1590 - Adolescente laureato - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1600 dated 16 juin 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1610.jpg Gloeden, Wilhelm von (1856-1931) - n. 1628 - da - Auch ich in Arkadien, p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 1641 - Nudo accademico maschile di fronte - Getty Museum - 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 1703 - G. Pedo, 130 Via Sistina Roma. Deponirt 1 Aug. 1900 (Galerie Bassenge).jpg Gloeden, Wilhelm von (1856-1931) - n. 1721.jpg Gloeden, Wilhelm von (1856-1931) - n. 1731 - Maria Intelisano - 1905 ca. - da - Sicilia mitica arcadia, p. 64.jpg Gloeden, Wilhelm von (1856-1931) - n. 1733 recto - Profilo vago (Maria Intelisano).jpg Gloeden, Wilhem von (1856-1931) - n. 1735.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 recto - Ritratto di Giacomo Lanfranchi travestito da ragazza - Ebay - Cm 16,8x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 (colorata a mano).jpg Gloeden, Wilhelm von (1856-1931) - n. 1741 recto - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1744 - Hypnos.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 - (Hypnos) - Perna, p. 25 & Leslie Lohman faundation.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 A recto - Russel p. 73 e Beautés siciliennes p. 47.jpg Gloeden, Wilhelm von (1856-1931) - n. 1836 recto - Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1868 recto - Due adolescenti nudi affiancati contro un muro - Auctionsfr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1870 - da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 2095 recto - Autoritratto come Nazareno - Sehnsucht, p. 27.jpg Gloeden, Wilhelm von (1856-1931) - n. 1948 recto - Due ragazzi nudi nel giardino di Gloeden - Getty Museum - Cm 16,7x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1994 - Nudo femminile accademico - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 2031.jpg Gloeden, Wilhelm von (1856-1931) - n. 2044 recto - Due ragazzi spalla a spalla davanti a una porta - Kiermeierp. 132.jpg Gloeden, Wilhelm von (1856-1931) - n. 2067 - da - The boys of Taormina, p. 31.jpg Gloeden, Wilhelm von (1856-1931) - n. 2071.jpg Gloeden, Wilhelm von (1856-1931) - n. 2169 - Beautés siciliennes, p.40.jpg Gloeden, Wilhelm von (1856-1931) - n. 1237 - Due giovani nella campagna. Cm 12x17,5. Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 2252 - Bacchino.jpg Gloeden, Wilhelm von (1856-1931) - n. 2274 - Beautés siciliennes, p. 55 e Bloomsbury auctions Italia.jpg Gloeden, Wilhelm von (1856-1931) - n. 2412 - Rosina Buciunì con fiori, cm 22x16,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 recto - cm 23x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 verso, Deposé 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 2448.jpg Gloeden, Wilhelm von (1856-1931) - n. 2755 recto - da - Auch ich in Arkadien p. 76.jpg Gloeden, Wilhelm von (1856-1931) - n. 2471 - Paradis sicilien, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 2483 recto - Tre bambini sotto a una croce - Beautés siciliennes, p. 101.jpg Gloeden, Wilhelm von (1856-1931) - n. 2528 A - Giovane moro Sehnsucht p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 r - Tunisian boy.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 v.jpg Gloeden, Wilhelm von (1856-1931) - n. 2556 - Tunisi - Porticato moresco - Sehnsucht p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 2600.jpg Gloeden, Wilhelm von (1856-1931) - n. 2675 - Sensucht p. 77.jpg Gloeden, Wilhelm von (1856-1931) - n. 2700 - ebay.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._2717.jpg Gloeden, Wilhelm von (1856-1931) - n. 2756 - Hotel Timeo & Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 2825 - Paradis sicilien, p. 53.jpg Gloeden, Wilhelm von (1856-1931) - n. 2836 = 1336.jpg Gloeden, Wilhelm von (1856-1931) - n. 2885 recto - Due giovani sulla scala della casa di Gloeden - L'arte di Gloeden, p. 125.jpg Gloeden, Wilhelm von (1856-1931) - n. 2936.jpg Gloeden, Wilhelm von (1856-1931) - n. 3091 B.jpg Reclining Male Nude Beside Vase - Wilhelm von Gloeden.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 B - Ragazzo con cappello in mano.jpg Gloeden, Wilhelm von (1856-1931) - n. 1015 recto - Ragazzo abruzzese - Timbrata. Cm 11x16.jpg Gloeden, Wilhelm von (1856-1931) - n. 1007 - Ragazzo e vecchio - Abruzzo, ca 1880 - Dal sito del National Geographic.jpg Gloeden, Wilhelm von (1856-1931) - n. 1685 - A naked Sicilian boy, in a rocky setting outdoors. Wellcome L0034527.jpg Gloeden, Wilhelm von (1856-1931) - n. 0349 - A Sicilian boy, posing naked by a doorway - Wellcome L0034528.jpg Gloeden, Wilhelm von (1856-1931) - n. 0524 - A draped youth and a naked woman, posing naked outdoors - Cm 16,9x22,5 - Wellcome L0034529.jpg Gloeden, Wilhelm von (1856-1931) - n. 0510 - Maria - Datata 1893.jpg|Maria, 1893 Plüschow, Wilhelm von (1852-1930) - n. ... - Youth crowned with flowers.jpg Gloeden, Wilhelm von (1856-1931) - n. 0146 recto - Three nude boys in front of a closed door - National Museum, Warsaw.jpg Gloeden, Wilhem von (1856-1931) - n. 1730 - Maria Intelisano.jpg Wilhelm von Gloeden (attr) Taormina Teatro.jpg Wilhelm von Gloeden Young male posed on cliff.jpg Wilhelm von Gloeden, Hypnos.jpg|''Hypnos'' §Gloeden. Wilhem von (1856-1931) - 1890 ca. - Autoritratto come arabo - Taschen p. 6.jpg Gloeden - LAB4.jpg Gloeden, Wilhelm von (1856-1931) - Costume siciliano.jpg|Garçon habillé en fille Gloeden, Wilhelm von (1856-1931) - n. 0089 recto - Tomba di August von Platen a Siracusa, nel 1900 - Ebay.png|Tomba di August von Platen a Siracusa nel 1900 </gallery> Outre ses nus masculins, von Gloeden a également réalisé des photographies de [[femme]]s, des [[paysage]]s, etc. lvrwlgttwabk2a9h6pvk741md7hytoz 765977 765976 2026-05-04T16:34:17Z Geoffroi 121497 ([[c:GR|GR]]) [[c:COM:FR|File renamed]]: [[File:Gloeden, Wilhelm von (1856-1931) - n. 2936.jpg]] → [[File:Gloeden, Wilhelm von (1856-1931) - n. 2936 recto - Tre bambini sulla riva del mare - Sehnsucht, p. 133.jpg]] [[c:COM:FR#FR1|Criterion 1]] (original uploader’s request) 765977 wikitext text/x-wiki {{Ph s Personnalités}} [[Image:Wilhelm von Gloeden (c. 1891).jpg|thumb|Autoportrait (1891)]] Le baron '''Wilhelm von Gloeden''', né à Wismar (Mecklembourg) le 16 septembre 1856 et mort à Taormina le 16 février 1931, était un photographe allemand. == Biographie == Wilhelm von Gloeden est considéré comme l'un des plus grands photographes de [[nu]] masculin et aussi comme l'un des pionniers de la photographie en plein air. Après avoir étudié l'histoire de l'art à Rostock, il suivit une formation de peintre. En 1878, pour soigner sa tuberculose, il se rendit, sur le conseil de son médecin, à Taormina en Sicile. Le peintre Otto Geleng qui y vivait déjà lui avait parlé de ce lieu paradisiaque. Émerveillé par les paysages siciliens, mais surtout par la beauté sauvage et antique des jeunes paysans et pêcheurs de Taormina, von Gloeden s'initia à la photographie, aidé aussi bien par les photographes locaux que par son cousin [[Wilhelm von Plüschow]] qui vivait à Naples et qui était, lui aussi, fasciné par le charme des jeunes Italiens du sud. Von Gloeden devint rapidement célèbre pour ses clichés d'éphèbes aux poses très inspirées de l'art antique. Ses photographies de nu masculin dégagent une puissance érotique peu égalée. Il reçut vers 1900 la visite d'un autre futur grand photographe du genre, [[Rudolph Lehnert]]. Très rapidement il devint très apprécié des esthètes de son temps qui lui commandaient des clichés : les écrivains Anatole France, Gabriele D'Annunzio, Oscar Wilde, Marcel Proust, mais aussi Richard Strauss, le ''Kaiser'' Guillaume II d'Allemagne, le Konprinz, Édouard VII, le roi d'Angleterre qui popularisa le nudisme, et même le roi du Siam. Plusieurs de ses photographies furent exposées et publiées dans les plus grands magazines spécialisés, ce qui peut sembler étonnant dans le contexte homophobe de l'époque. Si de nombreuses photographies de von Gloeden exaltent le désir homosexuel, elles ont été tolérées du fait de l'alibi de l'héritage culturel grec et surtout, parce qu'aucun de ses clichés n'est pornographique. Au début de la guerre en 1914, von Gloeden décida de rentrer en Allemagne. Lorsqu'il revint à Taormina, il avait beaucoup perdu de son inspiration et un peu de son goût pour la photographie. Beaucoup de ses modèles avaient péri à la guerre et les contraintes des normes sociales étaient désormais plus dures. Il mourut en 1931 et fut enterré près de sa sœur dans le cimetière protestant de Taormina. L'un de ses fidèles modèles et ami, Pancrazio Bucini, surnommé ''Il Moro'', hérita du fonds photographique (probablement quelques 7 000 clichés). Ces documents furent saisis par les fascistes en 1933 et 1936, environ soixante pour cent fuent détruits, et ''Il Moro'' fut condamné pour détention de photographies pornographiques, avant d'être ultérieurement acquitté. Finalement, l'œuvre du Baron est reconnue comme œuvre d'art et son jeune protégé exonéré. Il parvint à récupérer environ 800 négatifs. À sa mort en 1963, ''Il Moro'' les laissa à son fils qui les vendit à un antiquaire. Depuis l'an 2000, le fonds von Gloeden se trouve au Musée Alinari de Florence. Les tirages collectionnés durant le 20{{e}} siècle par les amateurs du travail de von Gloeden sont heureusement nombreux, tout comme les [[cartes postales]] et les catalogues d'expositions. Son œuvre rencontre encore de nos jours un vif intérêt, notamment dans la communauté homosexuelle. [[Roland Barthes]] a préfacé une monographie de von Gloeden. Si aujourd'hui Taormina continue de s'enorgueillir du séjour de von Gloeden, elle n'est plus vraiment ce qu'elle était au temps du Baron Von Gloeden ni des ses héritiers tel [[Konrad Helbig]]. == Publications == * Taormina [préface de Roland Barthes] .- Pasadena, Twelvetrees Press, 1986. {{ISBN|0942642228|9780942642223}} == Bibliographie == * CANET, Nicole .- Von Gloeden, [[Vincenzo Galdi]], Von Pluschow, Poésies Arcadiennes. Paris, Éditions Thélès, 2003. {{ISBN|2-84776-212-4}}. * CANET, Nicole .- Gloedeneries caravagesques : Von Gloeden, Von Pluschow, Vincenzo Galdi, nus masculins .- Paris, Au Bonheur du Jour, 2005. {{ISBN|2-9523322-1-5}} * CANET, Nicole .- Wilhem Von Gloeden, [[Photographie/Personnalités/P/Wilhelm von Plüschow|Guglielmo Pluschow]], [[Vincenzo Galdi]], Paradis Siciliens, Paysages, Portraits et Nus 1890 19052008. {{ISBN|2-9523322-5-8}} * FALZONE BARBARO, MIRAGLIA, Marina et MUSSA, Italo [avec une note de Goffredo Parise] .- Le fotografie di Von Gloeden .- Milan, Longansesi, 1980, 157 p. [diverses éditions de 1996 à 2000] * LEMAGNY, Jean-Claude .- Taormina, début de siècle .- Paris, Chêne, 1975, 105 p. {{ISBN|2851080423|9782851080424}} * PEYREFITTE, Roger .- Wilhelm von Gloeden .- Paris, Éditions Textes Gais, juillet 2008, biographie et cahier de 50 nus masculins. {{ISBN|2914679300|978-2914679305}} * Wilhelm von Gloeden, Wilhelm von Pluschow, Vincenzo Galdi, Italienische Jûnglings-photographien um 1900 .- Berlin, Janssen Verlag, 1991, {{ISBN|3-925443-11-8}} * Wilhelm von Gloeden, Wilhelm von Plüschow, Vincenzo Galdi, [http://www.aubonheurdujour.net/Beautes_Siciliennes.html Beautés Siciliennes], Éditions Nicole Canet, 2014 {{ISBN|978-2-9532351-7-3}}. == Voir aussi == * Biographie complète et galerie photo : http://vongloedengayhistory.free.fr/index.html * Biographie complète et galerie photo : http://www.aubonheurdujour.net/Gloeden.html {{T|[[Commons:Category:Wilhelm von Gloeden|voir d'autres photographies de von Gloeden sur Wikimedia Commons]]}} {{T|[[Commons:Catalogue of Wilhelm von Gloeden's pictures|voir le catalogue des œuvres de Wilhelm von Gloeden sur Wikimedia Commons]]}} == Galerie de photographies == <gallery widths="240px" heights="240px"> Gloeden, Wilhelm von (1856-1931) - n. 0000 - Pescatore.jpg Gloeden, Wilhelm von (1856-1931) - n. 0003 - Beauté siciliennes, p. 111.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0003 - Siesta greca.jpg Gloeden, Wilhelm von (1856-1931) - n. 0004r - Frate e ciabattino - 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 0009 - Bimbo - Galerie Bassenge - da - Beautés siciliennes, p. 117.jpg Gloeden, Wilhelm von (1856-1931) - n. 0011 - Madonna.jpg Gloeden, Wilhelm von (1856-1931) - n. 0021 - Da Amore e arte.jpg Gloeden, Wilhelm von (1856-1931) - n. 0022 - Serenata siciliana - Inexhaustible Italy, National Geographic 06-12 1916.jpg Gloeden, Wilhelm von (1856-1931) - n. 0033 B - Beauté siciliennes, p. 102 - Timbro W. von Gloeden Napoli (2).jpg Gloeden, Wilhelm von (1856-1931) - n. G 0027 - Il teatro greco di Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 0036 - da - Sicilia mitica Arcadia - p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0037B - Taormina - Teatro greco - Beautés siciliennes, p. 121 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0040, Beautés siciliennes, p. 33.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._0045_-_Terra_del_Fuoco.jpg Gloeden, Wilhelm von (1856-1931) - n. 0050 B - Mandorli - recto.tif Gloeden, Wilhelm von (1856-1931) - n. 0053 - Beautés siciliennes, p. 97.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 - Capri - Bambine - Galerie Bassenge - Beautés siciliennes, p. 113.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - recto - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0056 a - verso - Deposé 10 Mar 1904.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057 - Due uomini nudi seduti su un masso.jpg Gloeden, Wilhelm von (1856-1931) - n. 0057bis - Due uomini nudi di spalle - Cm 17x22,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 061 (deposé 1902) - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0067 - Pescatore - Beautés siciliennes, p. 95.jpg Gloeden, Wilhelm von (1856-1931) - n. 0063 - Da - Beautés siciliennes p. 36.jpg Gloeden, Wilhelm von (1856-1931) - n. 0064 - La confessione.jpg Gloeden, Wilhelm von (1856-1931) - n. 0065 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 0069 B & Crupi - Isola Bella e Capo Sant'Andrea - Perna p. 10.jpg Gloeden, Wilhelm von (1856-1931) - n. 0077 B - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 0081 grande formato.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0088 - (Metropolitan museum).jpg Gloeden, Wilhelm von (1856-1931) - n. 0090 - Tomba di August von Platen a Siracusa.jpg|Tomba di August von Platen a Siracusa nel 1900 Gloeden, Wilhelm von (1856-1931) - n. 0099 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 0365 - Due ragazzi in giardino - Cm 18x22.jpg Gloeden, Wilhelm von (1856-1931) - n. 0110 B - Taormina - Teatro Greco con autoritratto - Beautés siciliennes, p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 0121.jpg Gloeden, Wilhelm von (1856-1931) - n. 0123 - Ragazzo abruzzese nudo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0125 - Posillipo - Timbro e data 8 marzo 1899. Perna, p. 94.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0126 - Drout auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0134 recto - Chiostro di Morreale a Palermo.jpg Gloeden, Wilhelm von (1856-1931) - n. 0139 - Nerone. Da Perna, p. 20 - Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 0142 - Nerone Da Auch ich in Akadien, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 151 B - Taormina - Mandorli in fiore - Sehnsucht, p.68.jpg Gloeden, Wilhelm von (1856-1931) - n. 0152 - Debutdusiècle p. 29 & Leslie ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0174 - Twelwetrees p. 86 & Auch ich in Arkadien p. 125.jpg Gloeden, Wilhelm von (1856-1931) - n. 0185 - Beautés siciliennes, p. 114 - Campionario.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0210 recto - Hagar - Aste La Rosa.jpg Gloeden, Wilhelm von (1856-1931) - n. 0191, cm 18x24 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0195 - Perna p. 33.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0203 - Giovane arabo - Taschen p.79.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0208. Sigla BC e data 1909.jpg Gloeden, Wilhelm von (1856-1931) - n. 0222 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0224 - Peppino - Gallo, p. 17.jpg Gloeden, Wilhelm von (1856-1931) - n. 0225 - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - da - Amore e arte, p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 0241 - Les musiciens,1897 Tampon encré Gloeden au dos et date 1897.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 r - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0242 v - Deposirt 9 Oct 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 0243 - 1899 - Da ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0246 - 1914 - Paesaggio taorminese.jpg Gloeden, Wilhelm von (1856-1931) - n. 0249 - Debutdesiècle p. 73.jpg Gloeden, Wilhelm von (1856-1931) - n. 0255 B - Sito Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1258 - Amore e arte, p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0263 - Caino, ca. 1902.jpg|''Caino'' Gloeden, Wilhelm von (1856-1931) - n. G 0263 - Caino.jpg Gloeden, Wilhelm von (1856-1931) - n. 0283.jpg Gloeden, Wilhelm von (1856-1931) - n. 0287 recto - Due giovani drappeggiati su un sentiero del Monte Ziretto - Zannier, p. 152.jpg Gloeden, Wilhelm von (1856-1931) - n. 0299 - Janssen, p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0303 - da - Beautés siciliennes, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0307 - Janssen p. 23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 r - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0311 verso - Deponirt 1 Aug 1898.jpg Gloeden, Wilhelm von (1856-1931) - n. 0313 recto - Peppino Scavo vestito alla greca - ex collection Levy - Cm 16x23 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0317 B recto - Peppino Scavo vestito alla greca - Cm 17,2x23,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 0347 recto - Timbrata a secco Gaetano Pedo, Roma.jpg Gloeden, Wilhelm von (1856-1931) - n. 0352 - Beautés siciliennes, p. 57b.jpg Gloeden, Wilhelm von (1856-1931) - n. 0354 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0358 - da - Auch ich in Arkadien, p. 167.jpg Gloeden, Wilhelm von (1856-1931) - n. 0396 B recto - Zannier, p. 141.jpg Gloeden, Wilhelm von (1856-1931) - n. 0339 - Gloedeneries caravagesques, 2005, p. 28.jpg Gloeden, Wilhelm von (1856-1931) - n. 0401 recto - Due ragazzi accanto a due barche a una nassa - Ebay, cm 13x18.png Gloeden, Wilhelm von (1856-1931) - n. 0405 - Piazza San Domenico - Archivio Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 0410 - Photogravure from Nus, printed by Buchverlag, 1926.jpg Gloeden, Wilhelm von (1856-1931) - n. 0114 recto - Giuseppe Capasso nel Teatro greco. Cm 11,5x15,3.jpg Gloeden, Wilhelm von (1856-1931) - n. 0425 - da Et in Arcadia, p. 90.jpg Gloeden, Wilhelm von (1856-1931) - n. 0434 - Pietro di Napoli - Janssen p. 9.jpg Gloeden, Wilhelm von (1856-1931) - n. 0449 - Beautés siciliennes, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0445 recto - Due ragazzi nudi, con anfora e canna di bambù, accanto a un muro - Zannier p. 120, cm 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 0457 - Twelwetrees p. 88.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 recto - cm 17x23.jpg Gloeden, Wilhelm von (1856-1931) - n. 0463 verso - Deponiert Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0474 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0437 recto - Due ragazzi nudi abbracciati.jpg Gloeden, Wilhelm von (1856-1931) - n. 1149 - Caputo, p. 81.jpg Gloeden, Wilhelm von (1856-1931) - n. 0545 - 24c - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0549 - Paradis sicilien, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0550 B - recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0557 B recto - Rimbaud 2 p. 5 & Perna p. 62.jpg Gloeden, Wilhelm von (1856-1931) - n. 0574 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 0579.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 recto - Beautés siciliennes, p. 87.jpg Gloeden, Wilhelm von (1856-1931) - n. 0596 verso - Beautés siciliennes, p. 87 - Ristampato da Buciunì.jpg Gloeden, Wilhelm von (1856-1931) - n. 0611 recto - Due ragazzi nudi sulla riva del mare, cm 17x22,5 - Beautés siciliennes, p. 71.jpg Gloeden, Wilhelm von (1856-1931) - n. 0621 recto - Ex Texbraun Collection, Galerie David Guiraud, Paris.jpg Gloeden, Wilhelm von (1856-1931) - n. 0645 - 4-2-1899 - Da - Barthes p. 46 & Auch ich in Arkadien p. 89.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 r - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0669 v - Deponirt 26 Jul 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 0677.jpg Gloeden, Wilhelm von (1856-1931) - n. 0709 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 0717 - Siracusa latomie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0735 recto - Carlo Siligato e Pietro Mazza.jpg Gloeden, Wilhelm von (1856-1931) - n. G 0127 recto - Gloedeneries p. 14.jpg Plüschow, Wilhelm von (1852-1930) - n. 0767 recto - Ragazzino nudo di spalle - Paradis sicilien, p. 43 - Napoli, Cm 11,5 x16,8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0769 - Twelvetrees p. 21.jpg Gloeden, Wilhelm von (1856-1931) - n. 0776 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 0799 - Beautés siciliennes, p. 61.jpg Gloeden, Wilhelm von (1856-1931) - n. 0803 - da - Et in Arcadia ego, p. 74.jpg Gloeden, Wilhelm von (1856-1931) - n. 0804 - Levante p. 47 ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0831 recto - Pietro con un ramoscello sulle spalle, davanti a Mazzarò - Bonham auctions.jpg Gloeden, Wilhelm von (1856-1931) - n. 0842 - Carlotta.jpg Gloeden, Wilhelm von (1856-1931) - n. 0845 - Gallo, p 8.jpg Gloeden, Wilhelm von (1856-1931) - n. 0849 recto - Ragazzo drappeggiato, vestito da donna - Bassenge - Cm 17x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 0858 - Datato 12-9-1910 - Deposé 1903 - Cm 16,9x22, Turner p. 15.jpg Gloeden, Wilhelm von (1856-1931) - n. 0870 - Carlotta - 1890 ca. - Mussa, Italo - Wilhelm von Gloeden, ed. Malambri, 1980 p.....jpg Gloeden, Wilhelm von (1856-1931) - n. 0892 - Bimbo con stampella - Deponirt 1 Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 0917 - Minerva auctions déc 2012.jpg Gloeden, Wilhelm von (1856-1931) - n. 0922 - Zannier, p. 52.jpg Gloeden, Wilhelm von (1856-1931) - n. 0937 - Piccolo imperatore - da - Gallo, p.14.jpg Gloeden, Wilhelm von (1856-1931) - n. 0149 - Le tre grazie.jpg Gloeden, Wilhelm von (1856-1931) - n. 0996 - Twelvetrees p. 49.jpg Gloeden, Wilhelm von (1856-1931) - n. 1005.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 recto - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 verso - Il poeta.jpg Gloeden, Wilhelm von (1856-1931) - n. 1038 recto - Due uomini davanti a porta - Collezione privata.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._1039_-_da_-_Amore_e_arte,_p._92.jpg Gloeden, Wilhelm von (1856-1931) - n. 1050 recto - Quattro uomini nudi al mare - Schwulen Archiv Zürich - Cm 24x18.jpg Gloeden, Wilhelm von (1856-1931) - n. 1051 - Galerie au bonheur du jour ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1067 verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1077 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1080 - Paradis sicilien, p. 51.jpg Gloeden, Wilhem von (1856-1931) - n. 1094 - Nudo maschile con cane.jpg Gloeden, Wilhelm von (1856-1931) - n. 1104 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1115 - Twelvetrees p. 40 & Perna p. 80.jpg Gloeden, Wilhelm von (1856-1931) - n. 1117 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 - da - Auch ich in Arkadien, p. 165.jpg Gloeden, Wilhelm von (1856-1931) - n. 1126 recto.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B.jpg Gloeden, Wilhelm von (1856-1931) - n. 1155 B verso.jpg Gloeden, Wilhelm von (1856-1931) - n. 1160 (dated 1898).jpg Gloeden, Wilhelm von (1856-1931) - n. 1164.jpg Gloeden, Wilhelm von (1856-1931) - n. 1192 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1199 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1200 r.jpg Gloeden, Wilhelm von (1856-1931) - n. 1201 - Da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1211 - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1228 - da - Auch ich in Arkadien, p. 178 - ebay.jpg Gloeden, Wilhelm von (1856-1931) - n. 0066 B recto - Capolavori Alinari, p. 38.jpg Gloeden, Wilhelm von (1856-1931) - n. 1246 - Budapest library e Perna, p. 78.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 r - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1248 v - Deposé 23 Aug 1903 - From the Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1254 recto - Pasquale a torso nudo appoggiato a una roccia - Alinari.jpg Gloeden, Wilhelm von (1856-1931) - n. 1259 - 1899 - Taschen p.89.jpg Gloeden, Wilhelm von (1856-1931) - n. 1290 - Barthes p. 50 & Auch ich in Arkadien p. 169.jpg Gloeden, Wilhelm von (1856-1931) - n. 1310 - Timbrata - Beautés siciliennes, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 1348 - Napoli - Taschen p.51 e Pohlmann p. 140.jpg Gloeden, Wilhelm von (1856-1931) - n. 1355 - Getty Museum.jpg Gloeden, Wilhelm von (1856-1931) - n. 1360 - Barthes p. 52 e Auch ich in Arkadien, p. 106.jpg Gloeden, Wilhelm von (1856-1931) - n. 1365.jpg Gloeden, Wilhelm von (1856-1931) - n. 1368 - Deposé Aug 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1369 - deponiert 20 oct. 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1398.jpg Gloeden, Wilhelm von (1856-1931) - n. 1403 recto - Donna in costume siciliano - Cm 16,9x22,4.jpg Gloeden, Wilhelm von (1856-1931) - n. 1459.jpg Gloeden, Wilhelm von (1856-1931) - n. 1519 recto - "Sguardo alla preda" - Déposé 17 Lug 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1526 - cm 22x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 1531 - Deposé 2 Jan 1902.jpg Gloeden, Wilhelm von (1856-1931) - n. 1541 - deponirt 17 febr 1900.jpg Gloeden, Wilhelm von (1856-1931) - n. 1590 - Adolescente laureato - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 1600 dated 16 juin 1899.jpg Gloeden, Wilhelm von (1856-1931) - n. 1610.jpg Gloeden, Wilhelm von (1856-1931) - n. 1628 - da - Auch ich in Arkadien, p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 1641 - Nudo accademico maschile di fronte - Getty Museum - 18x24.jpg Gloeden, Wilhelm von (1856-1931) - n. 1703 - G. Pedo, 130 Via Sistina Roma. Deponirt 1 Aug. 1900 (Galerie Bassenge).jpg Gloeden, Wilhelm von (1856-1931) - n. 1721.jpg Gloeden, Wilhelm von (1856-1931) - n. 1731 - Maria Intelisano - 1905 ca. - da - Sicilia mitica arcadia, p. 64.jpg Gloeden, Wilhelm von (1856-1931) - n. 1733 recto - Profilo vago (Maria Intelisano).jpg Gloeden, Wilhem von (1856-1931) - n. 1735.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 recto - Ritratto di Giacomo Lanfranchi travestito da ragazza - Ebay - Cm 16,8x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1736 (colorata a mano).jpg Gloeden, Wilhelm von (1856-1931) - n. 1741 recto - Galerie Bassenge.jpg Gloeden, Wilhelm von (1856-1931) - n. 1744 - Hypnos.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 - (Hypnos) - Perna, p. 25 & Leslie Lohman faundation.jpg Gloeden, Wilhelm von (1856-1931) - n. 1748 A recto - Russel p. 73 e Beautés siciliennes p. 47.jpg Gloeden, Wilhelm von (1856-1931) - n. 1836 recto - Gérard Lévy collection.jpg Gloeden, Wilhelm von (1856-1931) - n. 1868 recto - Due adolescenti nudi affiancati contro un muro - Auctionsfr.jpg Gloeden, Wilhelm von (1856-1931) - n. 1870 - da - Auction.fr.jpg Gloeden, Wilhelm von (1856-1931) - n. 2095 recto - Autoritratto come Nazareno - Sehnsucht, p. 27.jpg Gloeden, Wilhelm von (1856-1931) - n. 1948 recto - Due ragazzi nudi nel giardino di Gloeden - Getty Museum - Cm 16,7x22,5.jpg Gloeden, Wilhelm von (1856-1931) - n. 1994 - Nudo femminile accademico - Sito del Musée d'Orsay.jpg Gloeden, Wilhelm von (1856-1931) - n. 2031.jpg Gloeden, Wilhelm von (1856-1931) - n. 2044 recto - Due ragazzi spalla a spalla davanti a una porta - Kiermeierp. 132.jpg Gloeden, Wilhelm von (1856-1931) - n. 2067 - da - The boys of Taormina, p. 31.jpg Gloeden, Wilhelm von (1856-1931) - n. 2071.jpg Gloeden, Wilhelm von (1856-1931) - n. 2169 - Beautés siciliennes, p.40.jpg Gloeden, Wilhelm von (1856-1931) - n. 1237 - Due giovani nella campagna. Cm 12x17,5. Timbrata.jpg Gloeden, Wilhelm von (1856-1931) - n. 2252 - Bacchino.jpg Gloeden, Wilhelm von (1856-1931) - n. 2274 - Beautés siciliennes, p. 55 e Bloomsbury auctions Italia.jpg Gloeden, Wilhelm von (1856-1931) - n. 2412 - Rosina Buciunì con fiori, cm 22x16,2.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 recto - cm 23x17.jpg Gloeden, Wilhelm von (1856-1931) - n. 2419 verso, Deposé 1903.jpg Gloeden, Wilhelm von (1856-1931) - n. 2448.jpg Gloeden, Wilhelm von (1856-1931) - n. 2755 recto - da - Auch ich in Arkadien p. 76.jpg Gloeden, Wilhelm von (1856-1931) - n. 2471 - Paradis sicilien, p. 54.jpg Gloeden, Wilhelm von (1856-1931) - n. 2483 recto - Tre bambini sotto a una croce - Beautés siciliennes, p. 101.jpg Gloeden, Wilhelm von (1856-1931) - n. 2528 A - Giovane moro Sehnsucht p. 119.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 r - Tunisian boy.jpg Gloeden, Wilhelm von (1856-1931) - n. 2536 v.jpg Gloeden, Wilhelm von (1856-1931) - n. 2556 - Tunisi - Porticato moresco - Sehnsucht p. 115.jpg Gloeden, Wilhelm von (1856-1931) - n. 2600.jpg Gloeden, Wilhelm von (1856-1931) - n. 2675 - Sensucht p. 77.jpg Gloeden, Wilhelm von (1856-1931) - n. 2700 - ebay.jpg Gloeden,_Wilhelm_von_(1856-1931)_-_n._2717.jpg Gloeden, Wilhelm von (1856-1931) - n. 2756 - Hotel Timeo & Taormina.jpg Gloeden, Wilhelm von (1856-1931) - n. 2825 - Paradis sicilien, p. 53.jpg Gloeden, Wilhelm von (1856-1931) - n. 2836 = 1336.jpg Gloeden, Wilhelm von (1856-1931) - n. 2885 recto - Due giovani sulla scala della casa di Gloeden - L'arte di Gloeden, p. 125.jpg Gloeden, Wilhelm von (1856-1931) - n. 2936 recto - Tre bambini sulla riva del mare - Sehnsucht, p. 133.jpg Gloeden, Wilhelm von (1856-1931) - n. 3091 B.jpg Reclining Male Nude Beside Vase - Wilhelm von Gloeden.jpg Gloeden, Wilhelm von (1856-1931) - n. 1031 B - Ragazzo con cappello in mano.jpg Gloeden, Wilhelm von (1856-1931) - n. 1015 recto - Ragazzo abruzzese - Timbrata. Cm 11x16.jpg Gloeden, Wilhelm von (1856-1931) - n. 1007 - Ragazzo e vecchio - Abruzzo, ca 1880 - Dal sito del National Geographic.jpg Gloeden, Wilhelm von (1856-1931) - n. 1685 - A naked Sicilian boy, in a rocky setting outdoors. Wellcome L0034527.jpg Gloeden, Wilhelm von (1856-1931) - n. 0349 - A Sicilian boy, posing naked by a doorway - Wellcome L0034528.jpg Gloeden, Wilhelm von (1856-1931) - n. 0524 - A draped youth and a naked woman, posing naked outdoors - Cm 16,9x22,5 - Wellcome L0034529.jpg Gloeden, Wilhelm von (1856-1931) - n. 0510 - Maria - Datata 1893.jpg|Maria, 1893 Plüschow, Wilhelm von (1852-1930) - n. ... - Youth crowned with flowers.jpg Gloeden, Wilhelm von (1856-1931) - n. 0146 recto - Three nude boys in front of a closed door - National Museum, Warsaw.jpg Gloeden, Wilhem von (1856-1931) - n. 1730 - Maria Intelisano.jpg Wilhelm von Gloeden (attr) Taormina Teatro.jpg Wilhelm von Gloeden Young male posed on cliff.jpg Wilhelm von Gloeden, Hypnos.jpg|''Hypnos'' §Gloeden. Wilhem von (1856-1931) - 1890 ca. - Autoritratto come arabo - Taschen p. 6.jpg Gloeden - LAB4.jpg Gloeden, Wilhelm von (1856-1931) - Costume siciliano.jpg|Garçon habillé en fille Gloeden, Wilhelm von (1856-1931) - n. 0089 recto - Tomba di August von Platen a Siracusa, nel 1900 - Ebay.png|Tomba di August von Platen a Siracusa nel 1900 </gallery> Outre ses nus masculins, von Gloeden a également réalisé des photographies de [[femme]]s, des [[paysage]]s, etc. 8ng9exmx7hxb8t1sdb8rgka82tx2al3 Fonctionnement d'un ordinateur/Les processeurs de traitement du signal 0 65767 765940 765931 2026-05-04T13:41:19Z Mewtow 31375 /* Les opérations arithmétiques d'un DSP */ 765940 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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 |} Il serait possible d'utiliser un registre général pour remplacer l'accumulateur, mais utiliser des accumulateurs a des avantages assez particuliers. 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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. 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. Les écritures de l'accumulateur dans les registres entrainent donc une perte de précision. Entre écrire dans les registres et écrire dans la mémoire RAM, c'est le même problème. Il vaut mieux éviter au maximum les transferts de l'accumulateur vers les registres. La conséquence est que les registres ajoutés servent surtout pour mémoriser des opérandes, Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 modes d'adressage d'un DSP=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===L'usage d'une architecture Harvard modifiée=== Les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, il faut utiliser une instruction pour charger le coefficient depuis la mémoire ROM, avant l'instruction MAC. Le DSP doit mémoriser ce coefficient dans un registre dédié, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande, celle de l'échantillon. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. Il faut rajouter une instruction LOAD T au processeur, afin de copier une donnée dans ce registre, et relier le registre T au bus de données. Rien d'impossible, mais le défaut est que <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Les architectures multiports ou hybrides=== Une solution alternative mémorise les coefficients d'un filtre FIR en mémoire RAM. Il s'agit d'une solution assez générale, qui marche aussi pour d'autres algorithmes que les filtres FIR. Le problème est que l'instruction MAC doit alors lire deux opérandes depuis la RAM. Et cela demande d'adapter le processeur pour gérer la situation Une première solution utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Le processeur peut alors lire les deux opérandes, une par une, et les enregistrer dans les registres. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. Ils se débrouillent avec moins d'une dizaine de registres. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Une autre solution est de lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> d2x73xeqmr6chs8agta1od79w4o8tn8 765941 765940 2026-05-04T13:47:07Z Mewtow 31375 /* Les opérations arithmétiques d'un DSP */ 765941 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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 |} Il serait possible d'utiliser un registre général pour remplacer l'accumulateur, mais utiliser des accumulateurs a des avantages assez particuliers. 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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. 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 modes d'adressage d'un DSP=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===L'usage d'une architecture Harvard modifiée=== Les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, il faut utiliser une instruction pour charger le coefficient depuis la mémoire ROM, avant l'instruction MAC. Le DSP doit mémoriser ce coefficient dans un registre dédié, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande, celle de l'échantillon. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. Il faut rajouter une instruction LOAD T au processeur, afin de copier une donnée dans ce registre, et relier le registre T au bus de données. Rien d'impossible, mais le défaut est que <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Les architectures multiports ou hybrides=== Une solution alternative mémorise les coefficients d'un filtre FIR en mémoire RAM. Il s'agit d'une solution assez générale, qui marche aussi pour d'autres algorithmes que les filtres FIR. Le problème est que l'instruction MAC doit alors lire deux opérandes depuis la RAM. Et cela demande d'adapter le processeur pour gérer la situation Une première solution utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Le processeur peut alors lire les deux opérandes, une par une, et les enregistrer dans les registres. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. Ils se débrouillent avec moins d'une dizaine de registres. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Une autre solution est de lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 75c0zd3qtznwejizhn4367l9o8hfri8 765943 765941 2026-05-04T13:54:00Z Mewtow 31375 /* L'architecture mémoire d'un DSP */ 765943 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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 |} Il serait possible d'utiliser un registre général pour remplacer l'accumulateur, mais utiliser des accumulateurs a des avantages assez particuliers. 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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. 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 modes d'adressage d'un DSP=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande, celle de l'échantillon. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Un défaut est que ces trois solutions ont de nombreux défauts. Les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T vu plus haut. L'opérande est copiée dans ce registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> d91wpp4kwb91zwxn4w4ovgh930qgquc 765944 765943 2026-05-04T13:57:57Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 765944 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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 |} Il serait possible d'utiliser un registre général pour remplacer l'accumulateur, mais utiliser des accumulateurs a des avantages assez particuliers. 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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. 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 modes d'adressage d'un DSP=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande, celle de l'échantillon. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 76iwzl3phbdfg25q109ik9pdxpumwix 765945 765944 2026-05-04T14:01:27Z Mewtow 31375 /* Les architectures hybrides registres-accumulateur */ 765945 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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 |} Il serait possible d'utiliser un registre général pour remplacer l'accumulateur, mais utiliser des accumulateurs a des avantages assez particuliers. 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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. 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 modes d'adressage d'un DSP=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande, celle de l'échantillon. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> noim8zizsd6499duhbcn5v447hwqg36 765946 765945 2026-05-04T14:02:04Z Mewtow 31375 /* Les architectures hybrides registres-accumulateur */ 765946 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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 |} Il serait possible d'utiliser un registre général pour remplacer l'accumulateur, mais utiliser des accumulateurs a des avantages assez particuliers. 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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. 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 modes d'adressage d'un DSP=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> naaxo88uww0f4yjlvkiiad2q6byt3a6 765948 765946 2026-05-04T14:08:01Z Mewtow 31375 /* Le jeu d'instruction d'un DSP */ 765948 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 9zcmn8tn4dp98du3hyuivx3gut7ky0x 765949 765948 2026-05-04T14:11:08Z Mewtow 31375 /* Le décaleur pour les arrondis */ 765949 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===L'unité de calcul MAC d'un DSP=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> fmdrihb2fbvivftqq4mb504qiken3lj 765950 765949 2026-05-04T14:12:35Z Mewtow 31375 /* L'unité de calcul MAC d'un DSP */ 765950 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> oertbkas4qit4xtphjl9tdcltdgg1tx 765951 765950 2026-05-04T14:12:43Z Mewtow 31375 /* Les unités MAC flottantes */ 765951 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== L'implémentation d'un circuit MAC pour opérandes entiers est très simple, car on peut fusionner l'additionneur et le multiplieur. Avec des opérandes flottants, on doit garder les deux séparés. Il n'est pas rare que l'instruction MAC soit pipelinée, histoire de pouvoir faire plus d'opérations MAC par cycle d'horloge. Cependant, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. Le registre en sortie du multiplieur est prévu pour mémoriser le résultat complet de la multiplication. Il a une taille deux fois plus grande que les opérandes. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Pour donner un exemple, le DSP TMS32010 de marque Texas Instrument disposait d'un additionneur et d'un multiplieur, couplés à trois registres : un registre accumulateur, et deux registres T et P pour les multiplications. Le registre T mémorisait le premier opérande d'une multiplication, la seconde opérande était lue depuis la mémoire RAM, le résultat était mémorisé dans le registre P. Une telle organisation était conçue pour faire des opérations MAC. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> rbfwplctailzvd19s1akecg9xqlb1wr 765952 765951 2026-05-04T14:16:13Z Mewtow 31375 /* L'implémentation matérielle de l'unité de calcul MAC */ 765952 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> cb2o3vnopmiyb01p3odgyaind6jms3u 765953 765952 2026-05-04T14:16:48Z Mewtow 31375 765953 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===Les architectures hybrides registres-accumulateur=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 4qwlobsxypqmly0nmnzj99e3curbwby 765957 765953 2026-05-04T15:22:02Z Mewtow 31375 /* Les architectures hybrides registres-accumulateur */ 765957 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. ===L'ajout de registres pour les opérandes=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Ils effectuent un traitement basique sur chaque échantillon, qui ne demande pas d'utiliser beaucoup de registres. 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 4169kqctmpdic3884otkadnfud28q08 765958 765957 2026-05-04T15:29:19Z Mewtow 31375 /* L'ajout de registres pour les opérandes */ 765958 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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. 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. Pour résoudre ce problème, il y a plusieurs solutions, que nous allons voir dans ce qui suit. Les DSPs utilisent rarement toutes ces solutions en même temps, chacun fait à sa sauce. Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===L'ajout de registres pour les opérandes=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. En conséquence, 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ===Les architectures multiports=== La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> cyrlozyuu64vb5gef4z5sj5ehkgntfd 765959 765958 2026-05-04T15:30:18Z Mewtow 31375 /* L'architecture mémoire d'un DSP */ 765959 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. En conséquence, 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Le fait qu'ils préfèrent lire les opérandes depuis un ''local store'' multiport fait qu'ils n'ont pas besoin de beaucoup de registres. Il est rare que les DSPs utilisent des registres généraux. À la place, ils préfèrent utiliser des registres spécialisés, avec un compteur de boucle, des registres pour les calculs d'adresse, un accumulateur, et éventuellement un ou deux registres pour les opérandes lus depuis la mémoire. Cette spécialisation des registres pose de nombreux problèmes pour les compilateurs, et 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. Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> hqps1w9005ab5ze2iexjzaxb4ah23xr 765960 765959 2026-05-04T15:31:21Z Mewtow 31375 /* Les registres et unités de calcul d'adresse d'un DSP */ 765960 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. En conséquence, 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> shlln6uj7e5pmoc2m00pfosx4w6qcho 765961 765960 2026-05-04T15:32:00Z Mewtow 31375 /* L'usage de registres spécialisés */ 765961 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. En conséquence, 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> oxsx8xnt42ceccdif4mp6f25uc42qts 765962 765961 2026-05-04T15:34:47Z Mewtow 31375 /* L'usage de registres spécialisés */ 765962 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. En conséquence, 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] ===L'usage d'une architecture Harvard modifiée=== Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> evoefcd2k0g666vmr4e54ytwreg4snu 765963 765962 2026-05-04T15:35:04Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 765963 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> Une solution plus élaborée ne se contente pas d'ajouter un seul registre T, mais plusieurs. Elle utilise une architecture hybride registres-accumulateur, qui dispose d'un accumulateur et de registres pour les opérandes. Quelques DSPs utilisent cette solution, même s'ils sont assez minoritaires. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. En conséquence, 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 grand maximum. Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> mq5452fj1ait91qi15ahb5378nn0zij 765964 765963 2026-05-04T15:38:26Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765964 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== Les DSPs se passent de registres généraux, car les algorithmes de traitement de signal n'en ont pas besoin. Les conditions pour utiliser des registres ne sont pas réunies. Pour rappel, les registres servent dans deux situations. La première est quand une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. La raison à cela est que les algorithmes de traitement de signal n'ont pas besoin de plus, comme on l'a dit plus haut. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> dvtotar9np5qh4igzdurl5cjx68lbyy 765965 765964 2026-05-04T15:38:51Z Mewtow 31375 /* L'usage de registres spécialisés */ 765965 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. La raison à cela est que les algorithmes de traitement de signal n'ont pas besoin de plus, comme on l'a dit plus haut. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Un point important est qu'il est possible de transférer les données de l'accumulateur vers ces registres d'opérandes. Cependant, cela demande de faire un arrondi, car les registres sont plus petits que l'accumulateur. Cependant, quelques DSPs utilisent des optimisations pour limiter la casse. Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] La majorité des DSPs utilise une autre solution : lire les deux opérandes en même temps, directement depuis la mémoire RAM, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> Dans les deux cas, la mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM peuvent être accédées en parallèle, ce qui permet de charger les deux opérandes d'une multiplication en même temps. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 8imrtnto8yn34klaaqgejw4dpt72tg2 765966 765965 2026-05-04T15:41:25Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765966 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> hy64kenqjos4maknu4qikuzk32vls25 765967 765966 2026-05-04T15:49:31Z Mewtow 31375 /* Les unités MAC flottantes */ 765967 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> Pour donner un exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres d'opérandes appelés X1, X2, Y1 et Y2. Les registres d'opérandes pouvaient être utilisés de deux manières : soit comme 4 registres de 24 bits, soit comme 2 registres de 48 bits. Il était possible de transmettre un résultat dans les registres d'opérandes en deux fois : une première étape pour transférer les 24 bits de poids fort une seconde pour les 24 bits de poids faible. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> msbmcul4bxysnch6y7fr28t5lovny6e 765968 765967 2026-05-04T16:07:33Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765968 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] D'autres DSPs séparent additionneur et multiplieur, mais sans mettre de registre entre les deux. Pour donner un autre exemple, le Motorola DSP 5600x avait deux accumulateurs, ainsi que 4 registres pour les opérandes des opérations MAC. Les registres pour les opérandes faisaient 24 bits, les deux accumulateurs en faisaient 56. Les deux accumulateurs étaient reliés à des circuits décaleur. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> cqnt8y1x053j79c6vy9yks9g6wiaj1u 765969 765968 2026-05-04T16:10:09Z Mewtow 31375 /* L'implémentation matérielle de l'unité de calcul MAC */ 765969 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques DSPs préfèrent utiliser un multiplieur séparé de l'additionneur, avec un registre entre les deux. Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} L'unité MAC intégre parfois le registre T vu plus haut. Pour donner un exemple, 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] ===Des exemples d'unités MAC=== Pour donner un exemple, 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> qa0tcmly4rx8e75brqidrzg4q2hk6qq 765970 765969 2026-05-04T16:11:18Z Mewtow 31375 /* L'implémentation matérielle de l'unité de calcul MAC */ 765970 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. ===Des exemples d'unités MAC=== Pour donner un exemple, 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 0qjtpc63x8t327zcdgq0f74wzax3v9m 765971 765970 2026-05-04T16:11:25Z Mewtow 31375 /* Des exemples d'unités MAC */ 765971 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. ===Des exemples d'unités MAC=== Pour donner un exemple, 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> pggz66n6jnvihup9eencfcqus9455ta 765972 765971 2026-05-04T16:11:42Z Mewtow 31375 /* Des exemples d'unités MAC */ 765972 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} Notez que l'usage d'un registre entre le multiplieur et l'additionneur permet de pipeliner l'unité de calcul MAC. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 8js5kgavorxjijh9mr36cxewlg45ixp 765973 765972 2026-05-04T16:22:37Z Mewtow 31375 /* L'implémentation matérielle de l'unité de calcul MAC */ 765973 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== 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, quelques 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. {| |[[File:Chemin de données d'un DSP.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] |[[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] |} 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. Pour l'addition, cela demande de court-circuiter le multiplieur, et d'envoyer lune opérande en entrée de l'additionneur. Pour cela, il faut intercaler un multiplexeur entre le multiplieur et l'additionneur. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] L'additionneur peut aussi être remplacé par une vraie unité de calcul entière, capable de faire des opérations bit à bit. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 79d7gxi64rbuag0dhq276eiu40cts3g 765975 765973 2026-05-04T16:32:32Z Mewtow 31375 /* L'implémentation matérielle de l'unité de calcul MAC */ 765975 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===L'implémentation matérielle de l'unité de calcul MAC=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. Pour l'addition, cela demande de court-circuiter le multiplieur, et d'envoyer lune opérande en entrée de l'additionneur. Pour cela, il faut intercaler un multiplexeur entre le multiplieur et l'additionneur. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] L'additionneur peut aussi être remplacé par une vraie unité de calcul entière, capable de faire des opérations bit à bit. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 7trf8mkljrmh2hevih1jntsupd9swkv 765979 765975 2026-05-04T20:13:05Z Mewtow 31375 /* L'instruction MAC et l'unité de calcul associée */ 765979 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. Pour l'addition, cela demande de court-circuiter le multiplieur, et d'envoyer lune opérande en entrée de l'additionneur. Pour cela, il faut intercaler un multiplexeur entre le multiplieur et l'additionneur. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] L'additionneur peut aussi être remplacé par une vraie unité de calcul entière, capable de faire des opérations bit à bit. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> cg11i1v4jnuhsekqoij26j6hloi7mcm 765980 765979 2026-05-04T20:20:56Z Mewtow 31375 /* L'instruction MAC et l'unité de calcul associée */ 765980 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] Un détail est que les opérandes envoyées à l'additionneur proviennent soit des registres, soit de la mémoire RAM. Elles sont donc 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 9j68a82tv5a0sn1pykuxfcdmkfagw7s 765981 765980 2026-05-04T20:23:53Z Mewtow 31375 /* L'implémentation matérielle de l'unité de calcul MAC */ 765981 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> La mémoire RAM doit être adaptée pour faire plusieurs accès mémoire par cycle. Une première solution, qui marche parfaitement pour les filtres FIR, est d'utiliser trois mémoires séparées : une qui contient les échantillons, une autre pour les coefficients, une autre pour les résultats. Les trois RAM 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. Une solution plus générale est 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=3|Architecture mémoire des DSP.]] Faisons un rapide rappel des trois méthodes précédentes : usage d'un registre T, usage de registres pour les opérandes, usage d'instructions ''load-op'' à accès mémoire multiples. Il est possible de modifier ces trois solution, en tenant compte que les DSPs utilisent tous une architecture Harvard, ce qui permet au processeur de charger une instruction en même temps que ses opérandes. Une des raisons est que les DSP "récents" utilisent un pipeline, ce qui implique de lire une instruction et de faire un accès mémoire dans le même cycle d'horloge. Vu que les DSPs n'ont pas de mémoire cache, ils doivent compenser en utilisant une architecture Harvard. Mais une autre raison est que cela permet de résoudre le problème mentionné plus haut. Les DSPs utilisent une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> o3hqu6m3rnesla3lrpiky48iowwmyyo 765982 765981 2026-05-04T20:30:11Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765982 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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. ===La lecture des opérandes pour l'instruction MAC=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] La solution précédente peut cependant être grandement améliorée, en utilisant une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> hqc69ch8phm0106yhxp2hmv1qwgxztv 765983 765982 2026-05-04T20:34:02Z Mewtow 31375 /* L'usage de registres spécialisés */ 765983 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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 via ''zero overhead looping''. 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=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] La solution précédente peut cependant être grandement améliorée, en utilisant une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> bc2b28wajeb383clbnsdod4y9ct7bfp 765984 765983 2026-05-04T20:34:37Z Mewtow 31375 /* L'usage de registres spécialisés */ 765984 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une des opérandes de la multiplication, situé juste avant l'unité de calcul MAC, que nous nommerons le '''registre T'''. [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] L'instruction MAC utilise alors implicitement le registre T, en plus de l'accumulateur. Elle a juste besoin de connaitre l'adresse de la seconde opérande. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> 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. 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 grand maximum. 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient LOAD (adresse opérande N) -> R0 ; LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] La solution précédente peut cependant être grandement améliorée, en utilisant une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> pmoz8s7a6lu1lsqm8hx8une96x0pe34 765989 765984 2026-05-04T20:45:45Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765989 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== La solution précédente peut cependant être grandement améliorée, en utilisant une architecture Harvard modifiée, qui 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. Ainsi, pour une instruction MAC d'un filtre FIR, une opérande est lue depuis la RAM, 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. 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. Il y a cependant des exceptions, mais c'est une des possibilités. Avec cette solution, trois implémentations sont possibles. La première réutilise le registre T ou les registres d'opérande vus plus haut. L'opérande est copiée dans un registre avec une instruction de lecture, puis l'instruction MAC est exécutée. Les deux instructions s'exécutent alors presque en même temps si le DSP a un pipeline. Il faut rajouter une instruction de lecture spécialisée, qui non seulement lit un coefficient en mémoire ROM, mais en plus enregistre la donnée lue dans le registre T au processeur. Et cela demande de relier le registre T au bus de données de la mémoire ROM. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD ROM adresse coefficient N ; //lecture dans le registre T MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Une autre solution intègre le chargement des deux opérandes dans l'instruction MAC. L'instruction MAC prend alors deux adresses mémoire comme opérande : une destinée à la mémoire ROM, une autre destinée à la mémoire RAM. Elle est utilisée sur les DSPs sans pipeline, ou avec. Elle donne cependant des gains en performance immédiat sur les DSPs sans pipeline. Mais surtout, elle permet d'éliminer le besoin d'avoir une mémoire multiport. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée.]] Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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 copier une donnée dans ce registre T, avant de lancer une instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivante // Calcul adresse opérande // Calcul adresse coefficient LOAD (adresse coefficient N) -> T ; MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> tgsbo4o6trvnlt4khokyu3iwpu6qa4f 765990 765989 2026-05-04T20:50:43Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 765990 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> t6tnax910h6byf0doqv9lr58p4upft5 765991 765990 2026-05-04T20:51:31Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 765991 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X// répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 1csrml1826t9qxwaks90v5jjl8lkr2v 765992 765991 2026-05-04T20:51:43Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765992 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X// répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X// répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X// répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 9gm7u1duvxiuyscb3jg2ht13lp89t2k 765993 765992 2026-05-04T20:51:51Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765993 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X// répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 3uppvxsc2dg1is6vjeztu1u2jr8fi7k 765994 765993 2026-05-04T20:52:02Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 765994 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N fois, l'instruction suivante MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> sm84y5bmcls5rzks0q068t8ixuhc7h0 765995 765994 2026-05-04T20:52:17Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 765995 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> fjlwt5ykf2rso1obugton4d3sesu9ki 765996 765995 2026-05-04T20:52:40Z Mewtow 31375 /* Les opérations arithmétiques d'un DSP */ 765996 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N fois, les X instructions suivantes // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 8ja8z5aemsyh3wtfcv6hda7ty61b8a1 765997 765996 2026-05-04T20:52:48Z Mewtow 31375 /* L'optimisation des boucles sur un DSP */ 765997 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> f0le529d2vz0d7dbn657jrilxwwugdm 765998 765997 2026-05-04T20:53:34Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765998 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande et coefficient MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> hh1szqoi5umrpozf5sl0jpcdgti9qzl 765999 765998 2026-05-04T20:53:46Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 765999 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC registre adresse N°1 -> R0 , registre adresse N°2 -> R1 ; </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 0d4u14pg66glmdnlz8mqxvex9uxah40 766000 765999 2026-05-04T20:54:50Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766000 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 -> R0 , RA1 -> R1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD (adresse coefficient N) ; //copie dans le registre T MAC adresse opérande N </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> bxnlv6umbqf4asogtcu0eaw3me065hf 766001 766000 2026-05-04T20:55:16Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 766001 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 -> R0 , RA1 -> R1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD (adresse opérande N) -> R0 , (adresse coefficient N) -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 3pncg3tb8slrmen6egeaty7yhlfl710 766002 766001 2026-05-04T20:55:31Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 766002 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 -> R0 , RA1 -> R1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 -> R0 , RA1 -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse coefficient // Calcul adresse opérande MAC adresse opérande N , adresse coefficient N </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 9ixv5yr0tdlfk0ykl2czj8j11xvcbgy 766003 766002 2026-05-04T20:55:50Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 766003 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 -> R0 , RA1 -> R1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 -> R0 , RA1 -> R1 ; </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAC RA0 , RA1 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> ln6lq2pb1n7afb7y7lvgsdhl345j06i 766004 766003 2026-05-04T20:56:03Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 766004 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 -> R0 , RA1 -> R1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. Le chargement des deux opérandes est intégré dans l'instruction MAC, avec les modes d'adressage adéquats. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAC RA0 , RA1 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> qd401x3vvl77xvksf6rjyg3zo6lmtk4 766005 766004 2026-05-04T20:57:25Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 766005 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 -> R0 , RA1 -> R1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. 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. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> niu7zgsh0az3z14aavsy9oo7ttgquie 766006 766005 2026-05-04T20:58:14Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766006 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. Avec ce mode d'adressage, le code d'un filtre FIR devient : <syntaxhighlight lang="asm"> // Configuration des registres d'adresse LOOP N X // répète les X instructions suivantes, N fois MAC RA0 , RA1 ; // on suppose que RA0 et RA1 sont deux registres d'adresse. </syntaxhighlight> 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. 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. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 17oe7ymiwzasd41voeoe1cwt4si2pqw 766007 766006 2026-05-04T20:58:38Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766007 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. 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. Au final, on fait bien un seul accès en mémoire RAM par instruction MAC. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> kbspct8oqajvl82vw7wvenlpg8cfoij 766008 766007 2026-05-04T21:01:42Z Mewtow 31375 /* L'usage d'une architecture Harvard modifiée */ 766008 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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, totalement. Ils préférent lire les deux opérandes directement dans la mémoire RAM, en même temps, sans avoir à passer par des registres ! En clair, les instructions des DSPs peuvent faire plusieurs accès mémoire en même temps. L'instruction MAC est alors une pure instruction ''load-op'', mais adaptée à l'usage de trois opérandes. Le code d'un filtre FIR devient alors : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 3fwn99c6blt6fib1tmzm2hx9gs1n530 766009 766008 2026-05-04T21:03:21Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 766009 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois LOAD RA0 ; //copie dans le registre T MAC RA1 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> j5beaasm8otmgw40j1lgfdxjsmy2t1g 766010 766009 2026-05-04T21:04:08Z Mewtow 31375 /* La lecture des opérandes pour l'instruction MAC */ 766010 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MUL RO , R1 -> R2 ADD R2 , R3 -> R3 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> kgdu6l3xec93o6s0lujbhl2fabqlhv3 766012 766010 2026-05-04T21:19:22Z Mewtow 31375 /* L'optimisation des boucles sur un DSP */ 766012 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 3y8f7ga47d9jrdcpkkdmcq9rsq464r1 766013 766012 2026-05-04T21:25:58Z Mewtow 31375 /* Des exemples d'unités MAC */ 766013 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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, avec ou sans arrondis/normalisation. 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> dyz6k5ir47fsp7sgv5s4z0t6w7c5zyf 766014 766013 2026-05-04T21:26:31Z Mewtow 31375 /* Des exemples d'unités MAC */ 766014 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> lff8zxvz8y1pkl9xk7vjxbrti4x63ow 766015 766014 2026-05-04T21:29:56Z Mewtow 31375 /* Le décaleur pour les arrondis */ 766015 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois // Calcul adresse opérande LOAD (adresse opérande N) -> R0 ; // Calcul adresse coefficient LOAD (adresse coefficient N) -> R1 ; MAC RO , R1 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 8x15v8o95708tcgf1m31mnrpvwdyjcx 766017 766015 2026-05-04T21:35:31Z Mewtow 31375 /* Les opérations arithmétiques d'un DSP */ 766017 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é avec un décalage, réalisé par le ''barrel shifter'', en fonction de l'exposant calculé. ===Des exemples d'unités MAC=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> li176fz24fqsta9xujnsxm1d6anhk83 766018 766017 2026-05-04T22:05:59Z Mewtow 31375 /* Les unités MAC flottantes */ 766018 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP ne contient pas qu'une unité de calcul MAC. En général, il contient aussi une seconde ALU entière, et un ''barrel shifter''. Les tout premiers DSP faisaient avec un circuit multiplieur, un additionneur/soustracteur, et un ''barrel shifter''. les DSP plus modernes remplacent le multiplieur avec le circuit MAC de la section précédente. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des opérations logiques et bit à bit, des additions/soustractions, guère plus. C'est une ALU entière classique, en somme. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> m6msw0qrwsbqaz9fk56f1cmaiiysbob 766019 766018 2026-05-04T22:07:45Z Mewtow 31375 /* Les unités de calcul d'un DSP */ 766019 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===Les unités de calcul d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière, séparée du circuit MAC, est utilisée pour exécuter des additions/soustractions, des opérations logiques et bit à bit, guère plus. C'est une ALU entière classique, en somme. Le ''barrel shifter'' est lui utilisé pour tronquer le résultat en sortie de l'accumulateur, pour le faire rentrer dans une adresse mémoire. Il est aussi utilisé pour gérer les nombres flottants par blocs, avec l'aide d'une unité dédiée aux exposants. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> bwpsvzsc2tsf7aplvia8555gmbivd5t 766020 766019 2026-05-04T22:26:50Z Mewtow 31375 /* La microarchitecture d'un DSP */ 766020 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===La microarchitecture d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Il y a en général deux unités de calcul d'adresse, une par opérande. [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> mlkjgytl4jnd8hca881m6gzdk6cnx7b 766021 766020 2026-05-04T22:28:20Z Mewtow 31375 /* La microarchitecture d'un DSP */ 766021 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===La microarchitecture d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Il y a en général deux unités de calcul d'adresse, une par opérande. 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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. 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 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. Elle travaille sur le contenu du registre T, qui mémorise une des opérande de la multiplication. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> gd6k94mvk6ixq41scmwachroes4g4q5 766022 766021 2026-05-04T22:28:46Z Mewtow 31375 /* La microarchitecture d'un DSP */ 766022 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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, qui se sont succédées dans le temps. Mais les frontières entre générations varient beaucoup d'un livre à l'autre, d'un auteur à l'autre. Je vais reprendre celle-ci, histoire de donner un apercu de l'évolution des DSPs : * Les DSPs de première génération étaient des architectures à accumulateur sous stéroïdes, avec de nombreux registres spécialisés. * La seconde génération a introduit des modes d'adressage spécialisés pour les files, ainsi que des optimisations pour les boucles. * Les nouvelles générations de DSP utilisent des jeux d'instruction dit VLIW ou SIMD. La première génération avait la même microarchitecture qu'une architecture à accumulateur, moyennant les ''guard bits'', l'usage de mémoires multiples ou multiports, et quelques détails du genre. La seconde génération a introduit des registres spécialisés dans les adresses et les indices, ainsi que la présence d'unités de calcul dédiées aux calculs d'adresse. Les nouvelles générations incorporent des optimisations microarchitecturales comme un pipeline, l'exécution superscalaire et quelques autres. Ils incorporent aussi des instructions SIMD, afin de gagner en performance, sans que cela pose problème pour le temps réel. 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===La microarchitecture d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Il y a en général deux unités de calcul d'adresse, une par opérande. 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 11wpyvbd12kl2dehmb7dkhrzstb43y0 766023 766022 2026-05-04T22:38:08Z Mewtow 31375 /* La microarchitecture d'un DSP */ 766023 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. En théorie, il serait possible d'utiliser des registres généraux et de mettre les adresses/indices/limites dedans. Le problème est que l'encodage des instructions serait alors assez complexe. Il devrait encoder trois numéros de registres par instruction d'accès mémoire : un pour l'adresse de base, un pour l'indice, un pour la limite. Or, les DSPs préfèrent utiliser des instructions courtes, pour limiter la taille du port de la mémoire ROM. Les DSPs ayant beaucoup de ports/bus, mieux vaut utiliser des ports assez petits. En utilisant un registre spécialisé pour l'adresse de base, un autre pour l'indice et un dernier pour la limite, ceux-ci peuvent être adressés implicitement. Pas besoin de les encoder dans l'instruction. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===La microarchitecture d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Il y a en général deux unités de calcul d'adresse, une par opérande. 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 2pp1qkbutzj81okoeym2j1ttvdskhah 766024 766023 2026-05-04T22:39:11Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766024 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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. ===Les registres et unités de calcul d'adresse d'un DSP=== Il est fréquent que les DSP aient des registres séparés pour les adresses, voire des registres d'indice. Il faut dire que cela simplifie grandement l'usage de l'adressage indirect/indicé sur les DSPs, qui sont avant tout des processeurs à accumulateurs. Ils sont aussi très utiles pour implémenter l'adressage modulo et bit-''reverse'', idem pour les registres d'indice. Les DSPs incorporent un banc de registre séparé pour les registres d'adresse, un autre pour les registres d'indice, ainsi qu'une unité de calcul d'adresse spécialisée. L'unité de calcul d'adresse implémente des modes d'adressages complexes, comme l'adressage modulo, l'adressage ''bit-reverse'', en plus des adressages indicés classiques. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===La microarchitecture d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Il y a en général deux unités de calcul d'adresse, une par opérande. 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> 0w1ceod9esyqumujbj97jqejxjajn6c 766025 766024 2026-05-04T22:40:55Z Mewtow 31375 /* Les registres et unités de calcul d'adresse d'un DSP */ 766025 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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. ===Les registres et unités de calcul d'adresse d'un DSP=== 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] Un DSP a souvent plusieurs unités de calcul, et plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ===La microarchitecture d'un DSP=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. A cela, il faut ajouter les unités de calcul d'adresse et le séquenceur. Il y a en général deux unités de calcul d'adresse, une par opérande. 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> dspiq3uk8fsbc0qt1kjsigmpt4hiqlz 766026 766025 2026-05-04T22:42:14Z Mewtow 31375 /* La microarchitecture d'un DSP */ 766026 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> njzcdibbns6i7b2brjcimrxn1vteu4p 766027 766026 2026-05-04T22:42:23Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766027 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. 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. {|class="wikitable" |- !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. Un DSP a souvent plusieurs copies de chaque registre d'adresse/indice. La raison est que cela permet de gérer plusieurs files en même temps. 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. ==L'architecture mémoire d'un DSP== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> rpb19076iidhnn68l6h8teg5kp633vi 766028 766027 2026-05-04T22:42:35Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766028 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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 en même temps. 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. 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> a1fumm1sndwskgefpc74nyufrm7lhzo 766029 766028 2026-05-04T22:43:18Z Mewtow 31375 /* Les modes d'adressage d'un DSP */ 766029 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> gcdpomlxd2lhweoh77sb1196p8pgo08 766030 766029 2026-05-04T22:43:30Z Mewtow 31375 /* La microarchitecture d'un DSP */ 766030 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===VLIW, SIMD et superscalarité=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> ab3rrivvoaar6uc0kocxnkmacqxxady 766031 766030 2026-05-04T22:43:42Z Mewtow 31375 /* VLIW, SIMD et superscalarité */ 766031 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== Un DSP contient une unité de calcul MAC, une ALU entière et un ''barrel shifter''. La seconde ALU entière est une ALU entière classique, 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===Les DSPs VLIW, SIMD et superscalaires=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> lxw5j0ar3x124l0vhf69u88nsv3za68 766032 766031 2026-05-04T22:57:28Z Mewtow 31375 /* La microarchitecture d'un DSP classique */ 766032 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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 ponderait 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. A chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, ce qui en fait des architectures à accumulateur. Les instructions MAD lisent une 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. A 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. {|class="wikitable" |- !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== 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=== 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 une opérande est lue 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 une opérande est chargée depuis la mémoire RAM, elle sera utilisée une seule fois. Un autre usage des registres est lié aux résultat 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=== 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 une 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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 grand maximum. 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 lues 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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édiat 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, une opérande est lue 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> Utiliser une architecture Harvard modifiée fonctionne bien pour implémenter des filtre FIR et de nombreux algorithmes de traitement de signal, mais elle est peu flexible. Avec cette solution, une des opérandes doit être constante et placée 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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. A 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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 instruction usuelles, sur des opérandes entières. 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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. Une opérande de l'addition provient de l'accumulateur et fait 40 bits, l'autre est une 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 une opérande de 32 bits, soit la taille adéquate. ===Les unités MAC flottantes=== Précisons que tout ce qui vient d'être dit précédemment ne fonctionne que pour des opérandes entières, pas pour des opérandes flottantes. Pour les opérandes flottantes, 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 protection 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 toutes 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é 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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ée 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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 mode d'adressage immédiat (constantes immédiates). [[File:Microarchitecture d'un DSP avec un registre T.png|centre|vignette|upright=2|Microarchitecture d'un DSP avec un registre T]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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 convertis en donnée de 16 ou 32 bits, ce qui demande de faire un décalage pour éliminer les bits de poids faible. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===Les DSPs VLIW, SIMD et superscalaires=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> junzkab8z7cd4iwjiutkvyxvgq7u5qe 766033 766032 2026-05-04T23:12:33Z Mewtow 31375 orthotypo 766033 wikitext text/x-wiki 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== 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. [[File:DSP block diagram.svg|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP.]] [[File:Dsp bloc fr.png|centre|vignette|upright=2|Chaine de traitement de signal utilisant un DSP, en français.]] ===Un flux de données échantillonné=== 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. [[File:Sampled.signal.svg|centre|vignette|upright=1.5|Signal échantillonné.]] 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=== 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 : : <math>y(t) = {\sum_{n=0}^{N-1}} b_n \cdot x[t - n]</math>, avec <math>b_n</math> le coefficient de l'échantillon à l'instant t-n. [[File:FIRdrekteForm.png|centre|vignette|upright=2|Représentation graphique d'un filtre FIR. Les échantillons à l'instant n sont notés u(n), T représente le délai entre deux échantillons.]] 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 : <syntaxhighlight lang="c"> int resultat = 0 ; for (i=0 ; i < N ; ++i) { resultat += coefficient[i] * echantillons[i] ; } </syntaxhighlight> 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. <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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 fonctionne lui aussi avec des instructions de multiplication et d'addition, sauf qu'il utilise deux variables. Appelons-les A et B. À chaque itération de boucle, on effectue les deux calculs : : <math>A = A + B \times \text{coefficient A}</math> : <math>B = B + A \times \text{coefficient B}</math> ===Les contraintes dites ''temps réel''=== 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== 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 : <syntaxhighlight lang="asm"> // 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 ; </syntaxhighlight> 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=== 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 : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> ===Les opérations arithmétiques d'un DSP=== 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. 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 un registre accumulateur pour simplifier ce genre de calcul, 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. [[File:Instruction MAD avec accumulateur d'un DSP 01.png|centre|vignette|upright=2|Instruction MAD avec accumulateur d'un DSP]] Avec l'usage d'une instruction MAC, le code d'un filtre FIR devient celui-ci : <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. Car il est possible d'utiliser deux accumulateurs, ce qui sert pour accélérer les transformées de Fourier. D'autres 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. {|class="wikitable" |+ Encodage d'une instruction MAC |- ! 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=== 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. [[File:Fonctionnement d'une file - 1.png|centre|vignette|upright=2|Fonctionnement d'une file.]] 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 : {| |- |[[File:Circular buffer - XX123XX with pointers.svg|vignette|upright=1.5|Circular buffer - XX123XX with pointers]] |- |[[File:Circular buffer - XX1234X with pointers.svg|vignette|upright=1.5|Circular buffer - XX1234X with pointers]] |- |[[File:Circular buffer - XXX234X with pointers.svg|vignette|upright=1.5|Circular buffer - XXX234X with pointers]] |- |[[File:Circular buffer - XXX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - XXX2345 with pointers]] |- |[[File:Circular buffer - 6XX2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6XX2345 with pointers]] |- |[[File:Circular buffer - 67X2345 with pointers.svg|vignette|upright=1.5|Circular buffer - 67X2345 with pointers]] |- |[[File:Circular buffer - 6782345 with pointers.svg|vignette|upright=1.5|Circular buffer - 6782345 with pointers]] |} 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. [[File:Circular buffer - 6789AB5 full.svg|centre|vignette|upright=2|File telle qu'utilisée sur un DSP.]] 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. {|class="wikitable" |- !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== 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=== 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=== 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> [[File:Implémentation de l'instruction MAC avec un registre d'opérande.png|centre|vignette|upright=2|Implémentation de l'instruction MAC avec un registre d'opérande]] 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''. <syntaxhighlight lang="asm"> LOOP N X // répète les X instructions suivantes, N fois MAD RA0 , RA1 </syntaxhighlight> 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. [[File:Architecture mémoire des DSP.png|centre|vignette|upright=2|Architecture mémoire des DSP.]] 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. [[File:Architecture mémoire des DSP avec deux mémoires séparées.png|centre|vignette|upright=2|Architecture mémoire des DSP avec deux mémoires séparées]] ===L'usage d'une architecture Harvard modifiée=== 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. <syntaxhighlight lang="asm"> 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 </syntaxhighlight> 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. [[File:Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.png|centre|vignette|upright=2|Implémentation de l'instruction MAC sur un DSP avec architecture Harvard modifiée, plus général.]] ===Le contrôleur DMA intégré à un DSP=== 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. [[File:DSP avec controleur DMA.png|centre|vignette|upright=2.5|DSP avec contrôleur DMA.]] ==L'instruction MAC et l'unité de calcul associée== 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. Vir cette unité de calcul devrait en théorie se faire 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''=== 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''. [[File:Instruction MAD avec accumulateur d'un DSP, avec guard bits.png|centre|vignette|upright=2|Chemin de données d'un DSP, avec guard bits et produit long]] 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=== 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. [[File:Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.png|centre|vignette|upright=2|Unité de calcul d'un DSP, avec un décaleur pour les arrondis finaux.]] 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=== [[File:Chemin de données d'un DSP.png|vignette|upright=1|Chemin de données d'un DSP, avec multiplieur et additionneur séparé.]] 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. [[File:Chemin de données d'un DSP, avec guard bits et produit long.png|centre|vignette|upright=1.5|Chemin de données d'un DSP, avec guard bits et produit long]] 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. [[File:Unité MAC modifiée de manière à supporter l'addition.png|centre|vignette|upright=2|Unité MAC modifiée de manière à supporter l'addition]] 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=== 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=== 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. [[File:Unité de calcul du DSP TMS32010 de marque Texas Instrument.png|centre|vignette|upright=2|Unité de calcul du DSP TMS32010 de marque Texas Instrument]] 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. [[File:Unité de calcul du Motorola DSP 5600x.png|centre|vignette|upright=2|Unité de calcul du Motorola DSP 5600x]] ==La microarchitecture d'un DSP== 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=== 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. [[File:Unité d'accès mémoire avec registres d'adresse ou d'indice.png|centre|vignette|upright=2|Unité d'accès mémoire avec registres d'adresse ou d'indice]] 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). [[File:Microarchitecture d'un DSP avec un registre T.png|centre|vignette|upright=2|Microarchitecture d'un DSP avec un registre T]] Si on suppose au contraire que le DSP utilise une mémoire RAM multiport, on devrait avoir ceci : [[File:Microarchitecture d'un DSP avec mémoire multiport.png|centre|vignette|upright=3|Microarchitecture d'un DSP avec mémoire multiport.]] 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 : [[File:Microarchitecture d'un DSP.png|centre|vignette|upright=3|Microarchitecture d'un DSP.]] Pour donner un exemple, prenons le DSP TMS320-C54x. 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. [[File:Chemin de données du TMS320C54x.png|centre|vignette|upright=2|Chemin de données du TMS320C54x]] ===Les DSPs VLIW, SIMD et superscalaires=== 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. [[File:Lucent DSP16xxx.png|centre|vignette|upright=2|Lucent DSP16xxx]] 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. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les ISA optimisés pour la compilation/interprétation | prevText=Les ISA optimisés pour la compilation/interprétation | next=Les architectures actionnées par déplacement | nextText=Les architectures actionnées par déplacement }} </noinclude> ez2p865d31kchdox1a9ioxmca9yumn1 Bonheur, morale, et philosophie épistolaire 0 71935 766016 688461 2026-05-04T21:35:17Z Xhungab 23827 /* L'amitié pour Descartes est un exemple de contentement moral */ 766016 wikitext text/x-wiki {{Navbar |1=[https://fr.wikibooks.org/w/index.php?title=Des_exercices_philosophiques.&oldid=602580/ Introduction]|3=[https://fr.wikibooks.org/w/index.php?title=Dossier_sur_l%27amiti%C3%A9_(cliquer)&oldid=601924/ Chapitre sur l'amitié]}} '''Dégager le sens de ce questionnement à partir d'exemples.''' (travail réalisé en classe en groupes) {{Cadre définition|titre=[https://www.canal-u.tv/video/ecole_normale_superieure_de_lyon/les_passions.3101/ les passions]:Définitions et précisions terminologiques|contenu= Écoutez cette présentation. Comment cette auteure définit-elle les passions? Comment cela se rattache-t-il au sujet?}} {| class="wikitable" |+Réponses élèves 4 GROUPES ![[groupe 1]] ![[groupe 2]] ![[groupe 3]] ![[groupe 4]] |} * Œuvres utilisées {{Cadre|1=[[s:Lettres à Lucilius|Correspondance entre Elisabeth et Descartes]]}} [[Fichier:Frans Hals, Portrait of René Descartes.jpg|gauche|vignette]] [https://streaming-canal-u.fmsh.fr/vod/media/canalu/videos/groupe_ens_lsh/descartes.et.le.sujet.copie._25673/2016.11.22_essentiels_shs_201_descartes_elisabeth.canalu.podcast.mp3/ Présentation d'Elisabeth] Auteur(s) : ANTOINE-MAHUT Delphine, DEL PRETE Antonella producteur : Université Ouverte des Humanités , ENS de Lyon / ENSMEDIA Réalisateur(s) : BOUDIN Sébastien [[Fichier:Attributed to Jean Dieu de Saint-Jean - Portrait de femme et d'enfant (Catherine Damicourt et son fils Roland ?) - Blois.jpg|vignette]]LA MUSIQUE BAROQUE ET L'EXPRESSION DES PASSIONS : ESQUISSE THÉORIQUE. [https://www.canal-u.tv/video/uds/la_musique_baroque_et_l_expression_des_passions_esquisse_theorique.19148 Intervention de Frédéric de Buzon] On a été frappé depuis longtemps par le fait que deux auteurs contemporains qui ne se connaissent nullement, René Descartes et Giulio Caccini, ont eu en commun une définition de la fin de la musique, celle de représenter, voire de provoquer des passions. Le fait que cette expression des passions, qui paraît culminer au XVIIe siècle italien dans les madrigaux de Monteverdi, et plus généralement dans le style représentatif à la naissance de l'opéra, figurant la colère, le désespoir, l'amour, la haine, le désir etc., soit aisément identifiable par l'auditeur ne dispense pas d'une réflexion plus théorique sur les moyens mis en œuvre dans cette expression et cette identification : comment savons-nous qu'une musique est triste ou gaie ? D'où vient ce qu'un poète et théoricien appelait sa « secrète énergie » ? Il s'agit d'une question classique, nullement limitée à la période baroque, qui est encore aujourd'hui discutée par de nombreux musicologues contemporains dans les domaines musicaux les plus divers. Le propos de cette intervention est d'examiner brièvement la situation du problème au début du XVIIe siècle, à partir de l'évocation de questions théoriques tels qu'elles se posent au moment d'un renouveau sans précédent des modes de rationalité philosophique et des moyens d'expression artistique. Il sera organisé à partir de la recherche des éléments musicaux auxquels est attribuée une fonction affective dans son rapport avec un texte: rythme et mesure, hauteur, modes, accents, timbre, à travers l'évocation de quelques moments de théorie ou de critique, par exemple de Pontus de Tyard, de Mersenne, ou de Descartes. On terminera sur un problème paradoxal avec lequel Descartes s'explique au tout début et à la toute fin de sa carrière philosophique, à savoir que l'on peut reconnaître une pièce de musique comme triste et néanmoins éprouver de la joie à l'entendre : preuve que ce n'est pas exactement la même chose que de représenter et de provoquer des passions.{{Cadre|1=[https://fr.wikisource.org/w/index.php?title=Lettres_%C3%A0_Lucilius&oldid=7004120/ Sénèque Lettres à Lucilius]}} [[Fichier:Tour-de-Seneque.jpg|gauche|vignette|la tour de Sénèque]] [[Fichier:Stèle funéraire de LUCILIUS.JPG|vignette|Lucilius ]] {{Cadre définition|titre=Les objectifs de ce wiki :|contenu=Étudier en groupe Les Lettres entre Descartes et Élisabeth Travail de recherche avec les élèves S'initier aux Humanités Numériques Réaliser un wikilivre collectif sur les diverses thématiques|cbord=violet|cfondtitre=#CCEEFF|cfondtexte=#EEF0FF}} [[Fichier:Seneque-Lettres a Lucilius 003.ogg|vignette|alt=|<ref>Sénèque - Lettres à Lucilius Date 18 septembre 2007 Source Travail personnel Auteur Augustin Brunault pour le site Littérature audio.com. Télécharger ce fichier au format MP3.</ref>Lectures de quelques lettres de Sénèque à Lucilius sur ce [https://commons.wikimedia.org/w/index.php?title=Category:S%C3%A9n%C3%A8que_-_Lettres_%C3%A0_Lucilius&oldid=53026414 lien] ]] {{Nouvelle page imprimée}} = Présentation des exercices = {{Début cadre|violet|titre=Travail en groupe}} On abordera en groupe cette question du rapport de la morale et du bonheur à partir des lettres de Descartes à Elisabeth {| class="wikitable mw-collapsible" |+<blockquote>RECHERCHES À RÉALISER pour les TRANSPOSER SUR UN WIKI</blockquote> !''Présenter la Princesse Élisabeth avec qui Descartes échange des lettres à propos de divers sujets, mais en particulier du bonheur'' |- |Présenter le contexte historique de ces lettres. |- |Réaliser un ebook sur la maladie et le bonheur. Rechercher dans le texte de Descartes les occurrences de la maladie. Quelles sont selon son analyse de la VIe partie du [https://fr.wikisource.org/w/index.php?title=Discours_de_la_m%C3%A9thode_(%C3%A9d._Cousin)&oldid=4000733/ Discours de la Méthode], les conditions physiques du bonheur ? Pourquoi est-il urgent de développer la technique ? |- |Repérer les passages où Descartes confronte morale et bonheur. Que reproche-t-il au mot « bonheur » ? À ce propos il parle d'hésitation, de choix sans réflexion. Expliquer en quoi le contentement est le résultat d'un choix volontaire et réfléchi. |} {{Fin cadre}} = Introduction à la problématisation philosophique de la question = Descartes est pris entre plusieurs traditions : - la tradition antique des stoïciens et d’Épicure et son Jardin - la chrétienté qui voit dans la charité, une générosité morale source de bonheur - le théâtre tragique et ses héros qui défend une autre conception de la générosité - ou tragi-comique comme Le Cid de Corneille - l'humanisme de Rabelais, Montaigne, La Boétie Qu'est-ce qui peut rendre l'homme « malheureux » ? === Les fausses conceptions du bonheur === ==== BONHEUR ET HASARD ==== {{Question|Quelle différence introduit Descartes entre les grandes âmes et les âmes vulgaires ?}} Mais il me semble que '''la différence qui est entre les plus grandes âmes et celles qui sont basses et vulgaires, consiste, principalement, en ce que les âmes vulgaires se laissent aller à leurs passions, et ne sont heureuses ou malheureuses, que selon que les choses qui leur surviennent sont agréables ou déplaisantes ; au lieu que les autres ont des raisonnements si forts et si puissants que, bien qu'elles aient aussi des passions, et même souvent de plus violentes que celles du commun, leur raison demeure néanmoins toujours la maîtresse, et fait que les afflictions même leur servent, et contribuent à la parfaite félicité dont elles jouissent dès cette vie. Car, d'une part, se considérant comme immortelles et capables de recevoir de très grands contentements, puis, d'autre part, considérant qu'elles sont jointes à des corps mortels et fragiles, qui sont sujets à beaucoup d'infirmités, et qui ne peuvent manquer de périr dans peu d'années, elles font bien tout ce qui est en leur pouvoir pour se rendre la fortune favorable en cette vie, mais néanmoins elles l'estiment si peu, au regard de l'éternité, qu'elles n'en considèrent quasi les événements que comme nous faisons ceux des comédies'''. Et comme les histoires tristes et lamentables, que nous voyons représenter sur un théâtre, nous donnent souvent autant de récréation que les gaies, bien qu'elles tirent des larmes de nos yeux ; ainsi ces plus grandes âmes, dont je parle, ont de la satisfaction, en elles-mêmes, de toutes les choses qui leur arrivent, même des plus fâcheuses et insupportables. Ainsi, ressentant de la douleur en leur corps, elles s'exercent à la supporter patiemment, et cette épreuve qu'elles font de leur force leur est agréable; ainsi, voyant leurs amis en quelque grande affliction, elles compatissent à leur mal, et font tout leur possible pour les en délivrer, et ne craignent pas même de s'exposer à la mort pour ce sujet, s'il en est besoin. Mais, cependant, le témoignage que leur donne leur conscience, de ce qu'elles s'acquittent en cela de leur devoir, et font une action louable et vertueuse, les rend plus heureuses, que toute la tristesse, que leur donne la compassion, ne les afflige. Et enfin, comme les plus grandes prospérités de la fortune ne les enivrent jamais, et ne les rendent point plus insolentes, aussi les plus grandes adversités ne les peuvent abattre ni rendre si tristes, que le corps, auquel elles sont jointes, en devienne malade. {{Question|Étude en groupe de [https://fr.wikisource.org/wiki/Correspondance_avec_%C3%89lisabeth/Descartes_%C3%A0_%C3%89lisabeth_-_Egmond,_18_mai_1645/ la lettre du 18 mai 1645]}} ==== LES PLAISIRS DU CORPS ==== ==== ''Madame, Je me suis quelquefois proposé un doute : savoir s’il est mieux d’être gai et content, en imaginant les biens qu’on possède être plus grands et plus estimables qu’ils ne sont, et ignorant ou ne s’arrêtant pas à considérer ceux qui manquent, que d’avoir plus de considération et de savoir, pour connaître la juste valeur des uns et des autres, et qu’on devienne plus triste. Si je pensais que le souverain bien fût la joie, je ne douterais point qu’on ne dût tâcher de se rendre joyeux, à quelque prix que ce pût être, et j’approuverais la brutalité de ceux qui noient leurs déplaisirs dans le vin, ou les étourdissent avec du pétun.'' ==== {{Question|Mieux vaut ne pas connaître la juste valeur des uns et des autres : pourquoi l'imagination se trouve-t-elle ici valorisée ?... Pour être finalement rejetée ? * Quelle est la source de nos illusions ?}} Mais '''je distingue''' '''entre le souverain bien''', qui consiste en l’exercice de la vertu, ou (ce qui est le même), en la possession de tous les biens, dont l’acquisition dépend de notre libre arbitre, e'''t la satisfaction d’esprit qui suit de cette acquisition.''' C’est pourquoi, voyant que c’est une plus grande perfection de connaître la vérité, encore. Mais lorsqu’on peut avoir diverses considérations également vraies, dont les unes nous portent à être contents, et les autres, au contraire nous en empêchent, il me semble que la prudence veut que nous nous arrêtions principalement à celles qui nous donnent de la satisfaction ; et même, à cause que presque toutes les choses du monde sont telles, qu’on les peut regarder de quelque côté qui les fait paraître bonnes, et de quelque autre qui fait qu’on y remarque des défauts, je crois que, si on doit user de son adresse en quelque chose, c’est principalement à les savoir regarder du biais qui les fait paraître le plus à notre avantage, pourvu que ce soit ... {{Question|Pourquoi Descartes insiste-t-il sur les excès ? À quelle science appartient la mesure ? La vertu morale est un exercice avant que d'être une théorie : expliquer Quel est le critère de l'action bonne ? À quel domaine renvoie « l'adresse » ? Expliquer en quoi la morale est un savoir-faire et que les actions humaines ne sont pas scientifiquement établies. En quoi la satisfaction personnelle vaut-elle mieux ? Ce n'est pas de l'égoïsme. Pourquoi ? |'''"pourvu que ce soit sans nous tromper."''' Expliquer le rôle de la volonté et son nécessaire contrôle par la raison. N'est-ce pas là une des figures de la morale ?}}Expliquer cette étonnante conclusion : la prudence veut que nous nous arrêtions principalement à celles qui nous donnent de la satisfaction ; et même, à cause que presque toutes les choses du monde sont telles, qu’on les peut regarder de quelque côté qui les fait paraître bonnes, et de quelque autre qui fait qu’on y remarque des défauts, je crois que, si on doit user de son adresse en quelque chose, c’est principalement à les savoir regarder du biais qui les fait paraître le plus à notre avantage, pourvu que ce soit ... ===La logique de l'excès=== {{Question|Les plaisirs tels la beuverie ou la drogue mènent-ils au bonheur ? Regarder : [https://www.canal-u.tv/video/cerimes/images_du_monde_visionnaire.10206/IMAGES DU MONDE VISIONNAIRE] Fonds Eric Duvivier Un film d'Henri Michaux, réalisé par Eric Duvivier. Musique Gilbert Amy. Ce film se propose de montrer les types d’images, et leurs façons spéciales d’apparaître et de disparaître, qu’un sujet quelconque, soumis à l’action de certaines substances psychotropes, voit défiler en son imagination avec une clarté extrême et sans l’intervention de sa volonté. Deux genres de visions, dont on a ici accusé les différences plutôt que les ressemblances, correspondent donc à deux hallucinogènes. '''À Noter''' l'importance de l'imagination et de l'illusion qui jouent contre la vérité pour parvenir à la joie. Il y a là quelque chose de surprenant, du moins du point de vue moral. La joie est excessive, à la recherche de la démesure - d'où l'emploi des superlatifs dans ce début de texte. À titre d'exemple, boire et se droguer sont des chemins faciles pour y parvenir. Qualifiant les adeptes de ce type de plaisirs « d'abrutis », on comprend très vite que Descartes désapprouve ces excès car impropres à l'homme raisonnable.}} {{Cadre|Du droit civil, je veulx que tu saiches par cueur les beaulx textes et me les confere avecques philosophie. Et, quand à la congnoissance desfaictz de nature, je veulx que tu te y adonne curieusement : qu'il n'y ayt mer, riviere ny fontaine, dont tu ne congnoisse les poissons ; tous les oyseaulx de l'air, tous les arbres, arbustes et fructices des forestz, toutes les herbes de la terre, tous les metaulx cachez au ventre des abysmes, les pierreries de tout Orient et Midy, rien ne te soit incongneu. Puis songeusement revisite les livres des medicins Grecs, Arabes et Latins, sans contemner les Thalmudistes et Cabalistes, et par frequentes anatomies, acquiers toy parfaicte congnoissance de l'aultre monde, qui est l'homme. Et, par lesquelles heures du jour commence à visiter les sainctes lettres : premierement, en Grec, le Nouveau Testament et Epistres des Apostres, et puis, en Hebrieu, le Vieulx Testament. Somme, que je voy un abysme de science. Car, doresnavant que tu deviens homme et te fais grand, il te fauldra yssir de ceste tranquillité et repos d'estude, et apprendre la chevalerie et les armes pour defendre ma maison, et nos amys secourir en tous leurs affaires contre les assaulx des malfaisans. Et veux que, de brief tu essaye combien tu as proffité, ce que tu ne pourras mieulx faire que tenent conclusions en tout sçavoir, publiquement, envers tous et contre tous, et hantant les gens lettrez qui sont tant à Paris comme ailleurs. Mais parce que, selon le saige Salomon, sapience n'entre poinct en ame malivole et science sans conscience n'est que ruine de l'ame, il te convient servir, aymer et craindre Dieu, et en luy mettre toutes tes pensées et tout ton espoir, et par foy formée de charité, estre à luy adjoinct, en sorte que jamais n'en soys désamparé par peché. Aye suspectz les abus du monde. Ne metz ton cueur à vanité, car ceste vie est transitoire, mais la parolle de Dieu demeure eternellement. Soys serviable à tous tes prochains et les ayme comme toy mesmes. Revere tes precepteurs ; fuis les compaignies des gens esquelz tu ne veulx point resembler, et, les graces que Dieu te a données, icelles ne reçoipz en vain. Et, quand tu congnoistras que auras tout le sçavoir de par delà acquis, retourne vers moy, affin que je te voye et donne ma benediction devant que mourir. Mon filz, la paix et grace de Nostre Seigneur soit avecques toy. Amen. De Utopie. ce dix septiesme jour du moys de mars. Ton père, GARGANTUA "}} {{Question|Il y a d'autres excès. L'érudition par exemple. Lire La lettre à Elisabeth du 3 novembre 1647 et comparer avec le texte ci-dessus de Rabelais : [https://fr.wikisource.org/wiki/Pantagruel/%C3%89dition_Marty-Laveaux,_1868/Chapitre_8#/ Pantagruel 8]. Quel est le risque avec l'érudition ? Constituer un dossier sur cette thématique}} ====La juste mesure==== {{Question|La juste mesure : en quoi les mathématiques sont-elles un modèle pour la morale? Même si on ne possède pas toutes les connaissances du fait d'un entendement fini, que propose Descartes dans ce texte ? La connaissance peut-elle tout? Expliquer la différence entre action et connaissance à partir de cette phrase dans le texte suivant : ''Mais on devrait plutôt se repentir, si on avait fait quelque chose contre sa conscience, encore qu’on reconnût, par après, avoir mieux fait qu’on n’avait pensé : car nous n’avons à répondre que de nos pensées ; et la nature de l’homme n’est pas de tout savoir, ni de juger toujours si bien sur-le-champ que lorsqu’on a beaucoup de temps à délibérer''}} ====== Lire ce passage de la lettre de Descartes du 6 octobre 1645 ====== ''Ainsi, lorsque Votre Altesse remarque les causes pour lesquelles elle peut avoir eu plus de loisir, pour cultiver sa raison, que beaucoup d’autres de son âge, s’il lui plaît aussi considérer combien elle a plus profité que ces autres, je m’assure qu’elle aura de quoi se contenter. Et je ne vois pas pourquoi elle aime mieux se comparer à elles, en ce dont elle prend sujet de se plaindre, qu’en ce qui lui pourrait donner de la satisfaction. Car la constitution de notre nature étant telle, que notre esprit a besoin de beaucoup de relâche, afin qu’il puisse employer utilement quelques moments en la recherche de la vérité, et qu’il s’assoupirait, au lieu de se polir, s’il s’appliquait trop à l’étude, nous ne devons pas mesurer le temps que nous avons pu employer à nous instruire, par le nombre des heures que nous avons eues à nous, mais plutôt, ce me semble, par l’exemple de ce que nous voyons communément arriver aux autres, comme étant une marque de la portée ordinaire de l’esprit humain. Il me semble aussi qu’on n’a point sujet de se repentir, lorsqu’on a fait ce qu’on a jugé être le meilleur au temps qu’on a dû se résoudre à l’exécution, encore que, par après, y repensant avec plus de loisir, on juge avoir failli. Mais on devrait plutôt se repentir, si on avait fait quelque chose contre sa conscience, encore qu’on reconnût, par après, avoir mieux fait qu’on n’avait pensé : car'' ''nous n’avons à répondre que de nos pensées ; et la nature de l’homme n’est pas de tout savoir, ni de juger toujours si bien sur-le-champ que lorsqu’on a beaucoup de temps à délibérer.'' ---- {{Question|Quelles sont les limites de la constitution humaine ? En quoi l'observation construit-elle un savoir ? À quelle science renvoie ici Descartes ? Pourquoi le regret est inutile ? Expliquer les difficultés de la liberté face au choix. Pourquoi la morale ne peut-elle prendre appui sur la connaissance ? (.à ce propos voir ce qu'écrit Descartes dans Le discours de la Méthode à propos de l'âne de Buridan)}} === Comment faire le bon choix ? Satisfaction et contentement. === Au reste, encore que la vanité qui fait qu’on a meilleure opinion de soi qu’on ne doit, soit un vice qui n’appartient qu’aux âmes faibles et basses, ce n’est pas à dire que les plus fortes et '''généreuses''' se doivent mépriser ; mais il se faut faire justice à soi-même, en reconnaissant ses perfections aussi bien que ses défauts ; et si la bienséance empêche qu’on ne les publie, elle n’empêche pas pour cela qu’on ne les ressente. Enfin, encore qu’on n’ait pas une science infinie, pour connaître parfaitement tous les biens dont il arrive qu’on doit faire choix dans les diverses rencontres de la vie, on doit, ce me semble, se contenter d’en avoir une médiocre des choses plus nécessaires, comme sont celles que j’ai dénombrées en ma dernière lettre. En laquelle j’ai déjà déclaré mon opinion, touchant la difficulté que Votre Altesse propose : savoir si ceux qui rapportent tout à eux-mêmes ont plus de raison que ceux qui se tourmentent pour les autres. Car si nous ne pensions qu’à nous seuls, nous ne pourrions jouir que des biens qui nous sont particuliers ; au lieu que, si nous nous considérons comme parties de quelque autre corps, nous participons aussi aux biens qui lui sont communs, sans être privés pour cela d’aucun de ceux qui nous sont propres. Et il n’en est pas de même des maux ; car, selon la philosophie, le mal n’est rien de réel, mais seulement une privation ; et lorsque nous nous attristons, à cause de quelque mal qui arrive à nos amis, nous ne participons point pour cela au défaut dans lequel consiste ce mal ; et quelque tristesse ou quelque peine que nous ayons en telle occasion, elle ne saurait être si grande qu’est la satisfaction intérieure qui accompagne toujours les bonnes actions, et principalement celles qui procèdent d’une pure affection pour autrui qu’on ne rapporte point à soi-même, c’est-à-dire de la vertu chrétienne qu’on nomme charité. Ainsi on peut, même en pleurant et prenant beaucoup de peine, avoir plus de plaisir que lorsqu’on rit et se repose. Et il est aisé de prouver que le plaisir de l’âme auquel consiste la béatitude, n’est pas inséparable de la gaieté et de l’aise du corps, tant par l’exemple des tragédies qui nous plaisent d’autant plus qu’elles excitent en nous plus de tristesse, que par celui des exercices du corps, comme la chasse, le jeu de la paume et autres semblables, qui ne laissent pas d’être agréables, encore qu’ils soient fort pénibles ; et même on voit que souvent c’est la fatigue et la peine qui en augmente le plaisir. Et la cause du contentement que l’âme reçoit en ces exercices, consiste en ce qu’ils lui font remarquer la force, ou l’adresse, ou quelque autre perfection du corps auquel elle est jointe ; mais le contentement qu’elle a de pleurer, en voyant '''représenter''' quelque action pitoyable et funeste sur un théâtre, vient principalement de ce qu’il lui semble qu’elle fait une action vertueuse, ayant compassion des affligés ; et généralement elle se plaît à sentir émouvoir en soi des passions, de quelque nature qu’elles soient, pourvu qu’elle en demeure maîtresse. {| class="wikitable" |+Questions !Si les représentations, au sens de préjugés n'ont pas leur place dans la connaissance - Descartes déploie le doute à ce propos- le philosophe justifie les représentations en un autre sens. Lequel? ! |- |" le contentement qu’elle a de pleurer, en voyant '''représenter''' quelque action pitoyable et funeste sur un théâtre, vient principalement de ce qu’il lui semble qu’elle fait une action vertueuse". À quelle analyse d'Aristote s'oppose ici Descartes | |- |" au lieu que, si nous nous considérons comme parties de quelque autre corps, nous participons aussi aux biens qui lui sont communs, sans être privés pour cela d’aucun de ceux qui nous sont propres." Quelles conséquences politiques se dégage de la position morale de Descartes ? | |- |'''Quel rapport entretiennent la morale et le contentement ?''' | |} {{Question|Dans cet extrait du 3 novembre 1645, Descartes rattache le contentement à l'estime d'autrui et à l'ami qui est unique :}} ''Il m'arrive si peu souvent de rencontrer de bons raisonnements, non seulement dans les discours de ceux que je fréquente en ce désert, mais aussi dans les livres que je consulte, que je ne puis lire ceux qui sont dans les lettres de Votre Altesse, sans en avoir un ressentiment de joie extraordinaire; et je les trouve si forts, que j'aime mieux avouer d'en être vaincu, que d'entreprendre de leur résister. Car, encore que la comparaison que Votre Altesse refuse de faire à son avantage, puisse assez être vérifiée par l'expérience, c'est toutefois une vertu si louable de juger favorablement des autres, et elle s'accorde si bien avec la générosité qui vous empêche de vouloir mesurer la portée de l'esprit humain par l'exemple du commun des hommes, que je ne puis manquer d'estimer extrêmement l'une et l'autre'' == La passion de générosité == {{Question|Pour approfondir expliquer ce paragraphe : en quoi la générosité est-elle la condition du contentement de n'importe qui quel que soit sa condition sociale ? Comparer avec la générosité selon Corneille. Le terme de "noble" a-t-il le même sens ? Construire une problématique de la "générosité" à partir de [https://www.littre.org/definition/g%C3%A9n%C3%A9reux/ la définition du Littré] }} [[Fichier:Gerard Philipe Warsaw National Theatre 1954.jpg|gauche|vignette]] ===Comment la générosité peut être acquise=== {{BlocCitation| Et il faut remarquer que ce qu'on nomme communément des vertus  sont des habitudes en l'âme qui la disposent à certaines pensées, en sorte qu'elles sont différentes de ces pensées, mais qu'elles les peuvent produire, et réciproquement être produites par elles. Il faut remarquer aussi que ces pensées peuvent être produites par l'âme seule, mais qu'il arrive souvent que quelques mouvements des esprits (1 les fortifient, et que pour lors elles sont des actions de vertu et ensemble des passions de l'âme ainsi, encore qu'il n'y ait point de vertu à laquelle il semble que la bonne naissance contribue tant qu'à celle qui fait qu'on ne s'estime que selon sa juste valeur, et qu'il soit aisé à croire que toutes les âmes que Dieu met en nos corps ne sont pas également nobles et fortes (ce qui est cause que j'ai nommé cette vertu générosité, suivant l'usage de notre langue, plutôt que magnanimité, suivant l'usage de l'École (2 où elle n'est pas fort connue) ; il est certain néanmoins que la bonne institution sert beaucoup pour corriger les défauts de la naissance, et que si on s'occupe souvent à considérer ce que c'est que le libre arbitre, et combien sont grands les avantages qui viennent de ce qu'on a une ferme résolution d'en bien user, comme aussi, d'autre côté, combien sont vains et inutiles tous les soins qui travaillent les ambitieux, on peut exciter en soi la passion et ensuite acquérir la vertu de générosité, laquelle étant comme la clef de toutes les autres vertus et le remède général contre tous les dérèglements des passions, il me semble que cette considération mérite bien d'être remarquée. |auteur= René Descartes, ''Traité des passions'' (1649), Éd. Gallimard, coll. Bibliothèque de la Pléiade, 1953, pp. 773 et 795.1. Il s'agit des « esprits animaux », particules très subtiles qui circulent dans nos nerfs.2. L’enseignement de la philosophie scolastique, étudiée au Moyen Âge à l'université.}} <div style="background: #3333ff40; border: 1px solid #330066; -moz-border-radius-topright: 0.5em; -moz-border-radius-topleft: 0.5em; font-size: 120%; padding: 0.3em; text-align: center;"> Le Cid Acte I sc 8 </div> <div style="border: 1px solid #ccc; border-top: 0; margin-bottom: 1em; padding: 0.3em;"> ''<small>Percé jusques au fond du cœur</small>'' ''<small>D’une atteinte imprévue aussi bien que mortelle,</small>'' ''<small>Misérable vengeur d’une juste querelle,</small>'' ''<small>Et malheureux objet d’une injuste rigueur,</small>'' ''<small>Je demeure immobile, et mon âme abattue</small>'' ''<small>Cède au coup qui me tue.</small>'' ''<small>Si près de voir mon feu récompensé,</small>'' ''<small>Ô Dieu, l’étrange peine !</small>'' ''<small>En cet affront mon père est l’offensé,</small>'' ''<small>Et l’offenseur le père de Chimène !</small>'' ''<small>Que je sens de rudes combats !</small>'' ''<small>Contre mon propre honneur mon amour s’intéresse :</small>'' ''<small>Il faut venger un père, et perdre une maîtresse :</small>'' ''<small>L’un m’anime le cœur, l’autre retient mon bras.</small>'' ''<small>Réduit au triste choix ou de trahir ma flamme,</small>'' ''<small>Ou de vivre en infâme,</small>'' ''<small>Des deux côtés mon mal est infini.</small>'' ''<small>Ô Dieu, l’étrange peine !</small>'' ''<small>Faut-il laisser un affront impuni ?</small>'' ''<small>Faut-il punir le père de Chimène ?</small>'' ''<small>Père, maîtresse, honneur, amour,</small>'' ''<small>Noble et dure contrainte, aimable tyrannie,</small>'' ''<small>Tous mes plaisirs sont morts, ou ma gloire ternie.</small>'' ''<small>L’un me rend malheureux, l’autre indigne du jour.</small>'' ''<small>Cher et cruel espoir d’une âme généreuse,</small>'' ''<small>Mais ensemble amoureuse,</small>'' ''<small>Digne ennemi de mon plus grand bonheur,</small>'' ''<small>Fer qui causes ma peine,</small>'' ''<small>M’es-tu donné pour venger mon honneur ?</small>'' ''<small>M’es-tu donné pour perdre ma Chimène ?</small>'' ''<small>Il vaut mieux courir au trépas.</small>'' ''<small>Je dois à ma maîtresse aussi bien qu’à mon père :</small>'' ''<small>J’attire en me vengeant sa haine et sa colère ;</small>'' ''<small>J’attire ses mépris en ne me vengeant pas.</small>'' ''<small>À mon plus doux espoir l’un me rend infidèle,</small>'' ''<small>Et l’autre indigne d’elle.</small>'' ''<small>Mon mal augmente à le vouloir guérir ;</small>'' ''<small>Tout redouble ma peine.</small>'' ''<small>Allons, mon âme ; et puisqu’il faut mourir,</small>'' ''<small>Mourons du moins sans offenser Chimène.</small>'' ''<small>Mourir sans tirer ma raison !</small>'' ''<small>Rechercher un trépas si mortel à ma gloire !</small>'' ''<small>Endurer que l’Espagne impute à ma mémoire</small>'' ''<small>D’avoir mal soutenu l’honneur de ma maison !</small>'' ''<small>Respecter un amour dont mon âme égarée</small>'' ''<small>Voit la perte assurée !</small>'' ''<small>N’écoutons plus ce penser suborneur,</small>'' ''<small>Qui ne sert qu’à ma peine.</small>'' ''<small>Allons, mon bras, sauvons du moins l’honneur,</small>'' ''<small>Puisqu’après tout il faut perdre Chimène.</small>'' ''<small>Oui, mon esprit s’était déçu.</small>'' ''<small>Je dois tout à mon père avant qu’à ma maîtresse :</small>'' ''<small>Que je meure au combat, ou meure de tristesse,</small>'' ''<small>Je rendrai mon sang pur comme je l’ai reçu.</small>'' ''<small>Je m’accuse déjà de trop de négligence :</small>'' ''<small>Courons à la vengeance ;</small>'' ''<small>Et tout honteux d’avoir tant balancé,</small>'' ''<small>Ne soyons plus en peine,</small>'' ''<small>Puisqu’aujourd’hui mon père est l’offensé,</small>'' ''<small>Si l’offenseur est père de Chimène <ref>https://fr.wikisource.org/w/index.php?title=Le_Cid&oldid=6979962</ref></small>'' </div> == Le bon usage de la liberté condition morale du contentement == === L'amitié pour Descartes est un exemple de contentement moral === {{cadre italien|titre = Vous trouverez dans ce [[Dossier sur l'amitié (cliquer)| Dossier sur l'amitié (cliquer)]] de quoi compléter la recherche|texte = Que permet la correspondance épistolaire ? En quoi est-ce différent d'un livre ? Comment s'y exprime la Princesse ? Met-elle en avant sa position sociale ? Quel est le rapport que La Princesse veut établir avec le philosophe ? Expliquer "et particulièrement pour moi". Quel est le sens du "je" dans la lettre d'Elisabeth ? Est-ce le même que celui de Descartes? Pour vous aider lire la Réponse de Descartes à Elisabeth : J'ai quasi peur que Votre Altesse ne pense que je ne parle pas ici sérieusement ; mais cela serait contraire au respect que je lui dois, et que je ne manquerai jamais de lui rendre. Et je puis dire, avec vérité, que la principale règle que j'ai toujours observée en mes études et celle que je crois m'avoir le plus servi pour acquérir quelque connaissance, a été que je n'ai jamais employé que fort peu d'heures, par jour, aux pensées qui occupent l'imagination, et fort peu d'heures, par an, à celles qui occupent l'entendement seul, et que j'ai donné tout le reste de mon temps au relâche des sens et au repos de l'esprit ; même je compte, entre les exercices de l'imagination, toutes les conversations sérieuses, et tout ce à quoi il faut avoir de l'attention. C'est ce qui m'a fait retirer aux champs ; car encore que, dans la ville la plus occupée du monde, je pourrais avoir autant d'heures à moi, que j'en emploie maintenant à l'étude, je ne pourrais pas toutefois les y employer si utilement, lorsque mon esprit serait lassé par l'attention que requiert le tracas de la vie. Ce que je prends la liberté d'écrire ici à Votre Altesse, pour lui témoigner que j'admire véritablement que, parmi les affaires et les soins qui ne manquent jamais aux personnes qui sont ensemble de grand esprit et de grande naissance, elle ait pu vaquer aux méditations qui sont requises pour bien connaître la distinction qui est entre l'âme et le corps {{Question| *Quel lien Descartes établit-il entre l'amitié et la morale ? *Quelle différence ce texte fait-il entre l'amitié et le copinage?}} Comparer avec le texte de Madame de Sévigné extrait d'un ensemble de [https://fr.wikisource.org/w/index.php?title=Lettres_choisies_de_Madame_de_S%C3%A9vign%C3%A9&oldid=5535576/ Lettres choisies] puis [https://fr.wikisource.org/w/index.php?title=Pascal_%C5%92uvres_compl%C3%A8tes_Hachette,_tome_2/Trois_discours_sur_la_condition_des_grands&oldid=7331891/ Les trois Discours de Pascal sur la condition des Grands] Mme de Sévigné : À Paris, lundi 15 décembre 1670.Je m’en vais vous mander la chose la plus étonnante, la plus surprenante, la plus merveilleuse, la plus miraculeuse, la plus triomphante, la plus étourdissante, la plus inouïe, la plus singulière, la plus extraordinaire, la plus incroyable, la plus imprévue, la plus grande, la plus petite, la plus rare, la plus commune, la plus éclatante, la plus secrète jusqu’à aujourd’hui, la plus brillante, la plus digne d’envie ; enfin une chose dont on ne trouve qu’un exemple dans les siècles passés : encore cet exemple n’est-il pas juste [1] ; une chose que nous ne saurions croire à Paris, comment la pourrait-on croire à Lyon ? une chose qui fait crier miséricorde à tout le monde ; une chose qui comble de joie madame de Rohan et madame d’Hauterive[2] ; une chose enfin qui se fera dimanche, où ceux qui la verront croiront avoir la berlue ; une chose qui se fera dimanche, et qui ne sera peut-être pas faite lundi. Je ne puis me résoudre à la dire, devinez-la, je vous le donne en trois ; jetez-vous votre langue aux chiens ? Hé bien ! il faut donc vous la dire : M. de Lauzun épouse dimanche au Louvre, devinez qui ? Je vous le donne en quatre, je vous le donne en dix, je vous le donne en cent. Madame de Coulanges dit : Voilà qui est bien difficile à deviner ! c’est madame de la Vallière. Point du tout, madame. C’est donc mademoiselle de Retz ? Point du tout ; vous êtes bien provinciale. Ah ! vraiment, nous sommes bien bêtes, dites-vous : c’est mademoiselle Colbert. Encore moins. C’est assurément mademoiselle de Créqui. Vous n’y êtes pas. Il faut donc à la fin vous le dire : il épouse, dimanche, au Louvre, avec la permission du roi, mademoiselle, mademoiselle de mademoiselle, devinez le nom ; il épouse Mademoiselle, ma foi ! par ma foi ! ma foi jurée ! Mademoiselle, la grande Mademoiselle, Mademoiselle, fille de feu Monsieur[3], Mademoiselle, petite-fille de Henri IV, mademoiselle d’Eu, mademoiselle de Dombes, mademoiselle de Montpensier, mademoiselle d’Orléans, Mademoiselle, cousine germaine du roi ; Mademoiselle, destinée au trône ; Mademoiselle, le seul parti de France qui fût digne de Monsieur. Voilà un beau sujet de discourir. Si vous criez, si vous êtes hors de vous-mêmes, si vous dites que nous avons menti, que cela est faux, qu’on se moque de vous, que voilà une belle raillerie, que cela est bien fade à imaginer ; si enfin vous nous dites des injures,nous trouverons que vous avez raison ; nous en avons fait autant que vous. Adieu ; les lettres qui seront portées par cet ordinaire vous feront voir si nous disons vrai ou non. DE Mme DE SÉVIGNÉ À M. DE COULANGES. 1 Anquetil croit que madame de Sévigné veut parler ici de Marie, sœur de Henri VII, roi d’Angleterre, et veuve de Louis XII, qui se remaria, trois mois après la mort du roi, au duc de Suffolk, qu’elle avait aimé avant d’être reine de France. 2 Marguerite, duchesse de Rohan, princesse de Léon, fille unique du duc de Rohan, célèbre dans l’histoire de nos guerres de religion, se maria par inclination, en 1645, avec Henri Chabot, simple gentilhomme sans fortune. Madame d’Hauterive, fille du duc de Villeroi, veuve du comte de Tournon et du duc de Chaulnes, se maria en troisièmes noces à Jean Vignier, marquis d’Hauterive, et depuis ce mariage son père ne voulut plus la voir. 3 Gaston de France, duc d’Orléans, frère de louis XIII.}} <span itemprop="associatedMedia" itemscope="" itemtype="http://schema.org/MediaObject"></span><span id="20._.E2.80.94_DE_Mme_DE_S.C3.89VIGN.C3.89_.C3.80_M._DE_COULANGES."></span><span class="pagenum ws-pagenum" id="76" title="Page:Sévigné - Lettres choisies, Didot, 1846.djvu/84"></span> {{Remarque| Organiser une correspondance philosophique entre deux classes}} {{Cadre|titre=La question de la maladie|contenu=Madame, Mon voyage ne pouvait être accompagné d'aucun malheur, puisque j'ai été si heureux, en le faisant, que d'être en la souvenance de Votre Altesse ; la très favorable lettre, qui m'en donne des marques, est la chose la plus précieuse que je pusse recevoir en ce pays. Elle m'aurait entièrement rendu heureux, si elle ne m'avait appris que la maladie qu'avait Votre Altesse, auparavant que je partisse de La Haye, lui a encore laissé quelques restes d'indisposition en l'estomac. Les remèdes qu'elle a choisis, à savoir la diète et l'exercice, sont, à mon avis, les meilleurs de tous, après toutefois ceux de l'âme, qui a sans doute beaucoup de force sur le corps, ainsi que montrent les grands changement que la colère, la crainte et les autres passions excitent en lui. Mais ce n'est pas directement par sa volonté qu'elle conduit les esprits dans les lieux où ils peuvent être utiles ou nuisibles ; c'est seulement en voulant ou pensant à quelque autre chose. Car la construction de notre corps est telle, que certains mouvements suivent en lui naturellement de certaines pensées : comme on voit que la rougeur du visage suit de la honte, les larmes de la compassion, et le ris de la joie. Et je ne sache point de pensée plus propre pour la conservation de la santé, que celle qui consiste en une forte persuasion et ferme créance, que l'architecture de nos corps est si bonne que, lorsqu'on est une fois sain, on ne peut pas aisément tomber malade, '''si ce n'est qu'on fasse quelque excès notable''', ou bien que l'air ou les autres causes extérieures nous nuisent ; et qu'ayant une maladie, on peut aisément se remettre par la seule force de la nature, principalement lorsqu'on est encore jeune. Cette persuasion est sans doute beaucoup plus vraie et plus raisonnable, que celle de certaines gens, qui, sur le rapport d'un astrologue ou d'un médecin, se font accroire qu'ils doivent mourir en certain temps et par cela seul deviennent malades, et même en meurent assez souvent, ainsi que j'ai vu arriver à diverses personnes. Mais je ne pourrais manquer d'être extrêmement triste, si je pensais que l'indisposition de Votre Altesse durât encore ; j'aime mieux espérer qu'elle est toute passée; et toutefois le désir d'en être certain me fait avoir des passions extrêmes de retourner en Hollande. Je me propose de partir d'ici, dans quatre ou cinq jours, pour passer en Poitou et en Bretagne, où sont les affaires qui m'ont amené ; mais sitôt que je les aurai pu mettre un peu en ordre, je ne souhaite rien tant que de retourner vers les lieux ou j'ai été si heureux que d'avoir l'honneur de parler quelquefois à Votre Altesse. Car, bien qu'il y ait ici beaucoup de personnes que j'honore et estime, je n'y ai toutefois encore rien vu qui me puisse arrêter. Et je suis, au-delà de tout ce que je puis dire, etc. {| class="wikitable" |} {{Nouvelle page imprimée}}<big></big><blockquote></blockquote> = Le bonheur selon Sénèque = __INDEX__ __LIENNOUVELLESECTION__ === Œuvre : [https://fr.wikisource.org/w/index.php?title=Lettres_%C3%A0_Lucilius&oldid=7004120/Lettres à Lucilius]=== Ces lettres n'ont pas la tonalité des Lettres de Descartes et Elisabeth. Quel est le rapport établi par Sénèque avec son correspondant ? Il lui prête plusieurs visages : lesquels ? Qui est Lucilius ?<span itemprop="associatedMedia" itemscope="" itemtype="http://schema.org/MediaObject"></span> |LETTRE LXXXV. '''Que le sage s’interdise même les passions les plus modérées.''' Je t’avais ménagé ; j’avais omis tout ce qui restait encore de trop difficile à démêler, satisfait de te donner comme un avant-goût de ce que disent les nôtres pour établir que la vertu à elle seule suffit à remplir toutes les conditions du bonheur. Tu veux que je réunisse tous les arguments soit de notre école, soit imaginés pour nous persifler : si je l’entreprenais, au lieu d’une lettre je ferais un livre, moi qui témoigne à tout instant que ce genre de démonstration est loin de me plaire. J’ai honte de descendre dans la lice, pour la cause des dieux et des hommes, avec une alène pour toute arme. « Qui est prudent est aussi tempérant ; l’homme tempérant a de plus la constance ; la constance suppose l’imperturbabilité, laquelle n’admet point d’affection triste ; or qui est libre de tristesse est heureux : donc l’homme prudent est heureux, et la prudence suffit pour le bonheur. » À cette série de déductions, des péripatéticiens répondent que l’imperturbabilité, et la constance, et l’absence de tristesse s’attribuent, dans leur langage, à l’homme qui n’est troublé que rarement et médiocrement, et non pas qui ne l’est jamais ; l’exemption de tristesse, ils l’entendent de quiconque n’y est point enclin et ne se livre pas fréquemment ou avec excès à ce genre de faiblesse : car notre nature ne veut pas qu’aucune âme en soit affranchie ; leur sage, invincible au chagrin, y est toutefois vulnérable... et le reste dans le même sens, suivant l’esprit de leur secte. Ils n’excluent pas les passions, ils les atténuent. Mais que c’est accorder peu au sage que de le dire plus fort que les plus faibles, plus gai que les plus affligés, plus modéré que les plus fougueux, plus grand que l’extrême bassesse ! Et que n’admire-t-il son agilité, que n’en est-il fier en considérant les boiteux et les estropiés ? Elle aurait pu courir sur le front des épis, Sans froisser ni courber ce flexible tapis, <span class="pagenum ws-pagenum" id="247" title="Page:Sénèque - Œuvres complètes, trad. Baillard, tome II.djvu/257"></span> Ou de son pied léger suspendu sur l’abîme, Sans l’y mouiller jamais, des flots raser la cime[1]. Voilà la vitesse qu’on estime par elle-même, et non pas celle qu’on loue comparativement aux plus lentes allures. Appellerais-tu bien portant l’homme attaqué de fièvre, même légère ? Un degré moindre dans le mal n’est pas la bonne santé. « Le sage, disent-ils, est appelé imperturbable comme on appelle fruits sans noyau non ceux qui n’en ont point, mais ceux qui l’ont fort petit. » Erreur. Ce n’est pas la diminution, c’est l’absence des vices qui constitue l’homme vertueux tel que je le conçois ; il faut qu’ils soient nuls, non pas moindres ; si peu qu’il y en ait, on les verra croître et lui faire obstacle à chaque pas. Si une fluxion sur les yeux, grandie jusqu’au dernier période, ôte la vue, un commencement de fluxion la trouble. Donne au sage des passions quelconques, la raison, impuissante contre elles, sera emportée comme par un torrent, d’autant plus qu’au lieu d’une seule, c’est la ligue entière des passions que tu lui laisses à combattre. Or cette masse réunie, tout médiocre que soit chaque ennemi, est plus forte que le choc d’un seul, si grand qu’il puisse être. Ce sage a pour la richesse un amour qui est modéré, de l’ambition sans trop de fougue, une colère qu’on peut apaiser, une légèreté moins vagabonde et moins mobile que bien d’autres, un goût de débauche qui n’est point de la frénésie. Mieux partagé serait l’homme qui aurait un seul vice complet que celui qui, à moindre dose, les réunirait tous. D’ailleurs qu’importe le degré de la passion ? Quel qu’il soit, elle ne sait pas obéir, elle ne reçoit pas de conseil. Tout comme nul animal, soit sauvage, soit domestique ou privé, n’obtempère à la raison, parce que leur nature est d’être sourds à sa voix ; de même les passions ne suivent ni n’écoutent, si minimes qu’elles soient. Les tigres ni les lions ne dépouillent jamais leur férocité, bien qu’elle plie quelquefois ; et lorsqu’on s’y attend le moins, cette rage un instant radoucie se réveille terrible. Jamais le vice ne s’est franchement apprivoisé. Enfin, où la raison triomphe, les passions ne naîtront même point ; où elles naissent malgré la raison, malgré elle elles gagnent du terrain. Car il est plus facile de les arrêter au début que de régler leur fougueux développement[2]. <nowiki>Mensonge donc et danger que ce moindre degré dans le mal, système à mettre au même rang que celui qui dirait : « Sois modérément fou, modérément malade. » La vertu seule garde ce tempérament, que n’admettent point les mauvaises affections de l’âme : on les expulse plus aisément qu’on ne les dirige. N’est-il pas vrai que ces vices, invétérés et endurcis, qu’on appelle maladies de l’âme, sont immodérés, comme l’avarice, la cruauté, la tyrannie, l’impiété ? Les passions le sont donc aussi : car des passions on passe aux vices. Et puis, pour peu que tu laisses d’empire à la tristesse, à la crainte, à la cupidité, à tout mouvement dépravé de l’âme, tu n’en seras plus maître. Pourquoi ? Parce que c’est hors de toi qu’ils trouvent leurs stimulants. Aussi se développeront-ils selon que ces causes d’excitation seront plus ou moins énergiques. La crainte sera plus grande si l’objet qui la frappe semble plus grave ou plus imminent ; et le désir d’autant plus vif que de plus riches avantages éveilleront nos espérances. Si la naissance des passions dans l’homme ne dépend pas de l’homme, il dépend aussi peu de lui de les avoir à tel degré. Si tu leur permets de commencer, elles s’accroîtront avec leurs causes et toujours en proportion de celles-ci[3]. Ajoutons que même les plus petites affections de l’âme ne peuvent que grandir : jamais le mal ne garde de mesure. Les maladies les plus légères au début n’en suivent pas moins leur marche, et parfois une aggravation toute minime perd le malade. Mais quelle folie n’est-ce pas de croire qu’une chose dont le commencement ne dépend point de nous, prenne fin quand il nous plaira ! Comment suis-je assez fort pour étouffer ce que je n’ai pu empêcher de se produire, bien qu’il soit plus aisé de fermer la porte à l’ennemi que de le maîtriser une fois reçu ?</nowiki> On a distingué, on a dit : « L’homme tempérant et sage, tranquille par sa complexion morale et physique, ne l’est point par le fait des événements. Si en effet, dans l’habitude de son âme, il ne sent ni trouble, ni tristesse, ni crainte, une foule de causes surgissent du dehors qui s’en viennent le troubler. » Voici ce qu’on veut dire par là : Il n’est point colère et se fâche pourtant quelquefois ; sans être timide, il a quelquefois peur : en d’autres termes, la crainte n’est pas en lui comme vice, mais comme impression. Admettons l’hypothèse ; et la fréquence des impressions produira le vice ; et la colère, admise dans l’âme, refondra cette constitution morale où la colère n’avait point part. Je dis plus : qui ne méprise pas les accidents du dehors craint donc quelque chose ; et lorsqu’il faudra braver hardiment et en face les glaives et les feux pour la patrie, les lois, la liberté, il marchera de mauvaise grâce et à contre-cœur. Le sage ne tombera jamais dans cette discordance de sentiments. Il faut prendre garde aussi, ce me semble, de confondre deux points qui veulent être établis séparément. On conclut de la nature même de la chose qu’il n’y a de bien que l’honnête, et pareillement, que la vertu suffit pour le bonheur. S’il n’y a de bien que l’honnête, tout le monde accordera que pour vivre heureusement il suffit de la vertu ; réciproquement, si la vertu seule fait le bonheur, on ne disconviendra pas que l’unique bien c’est l’honnête. Xénocrate et Speusippe tiennent que le bonheur peut à toute force être le fruit de la vertu seule, et que cependant l’honnête n’est pas l’unique bien. Épicure aussi est d’avis qu’avec la vertu l’homme est heureux ; mais qu’en elle-même la vertu n’est point assez pour le bonheur, vu qu’on est heureux par la volupté, qui procède de la vertu, mais qui n’est point la vertu même. – Inepte distinction ! car il dit lui-même que jamais la vertu n’existe sans la volupté. D’après quoi, si toujours elle lui est inséparablement unie, seule elle suffira pour le bonheur, puisqu’elle a avec elle la volupté, puisqu’elle ne va point sans elle, lors même qu’elle est seule. Autre absurdité quand on dit qu’à toute force on sera heureux par la vertu, mais non parfaitement heureux : je ne vois pas comment cela peut se faire. Car la vie heureuse comprend le bien parfait et à son comble : elle est donc parfaitement heureuse. Si celle des dieux n’offre rien de plus grand ni de meilleur ; si la vie heureuse c’est la vie divine, il n’est plus pour elle d’accroissement possible. Et encore, si la vie heureuse est celle qui n’a faute de rien, toute vie heureuse l’est parfaitement ; là se trouve le bonheur et le bonheur suprême. Douteras-tu que la vie heureuse ne soit le souverain bien ? Donc, si elle possède ce bien, elle est souverainement heureuse. Le souverain bien n’étant point susceptible d’augmenter, car qu’y aurait-il au delà du terme le plus élevé ? il en est de même de la vie heureuse qui ne le serait pas sans le souverain bien. Que si tu fais l’un plus heureux que l’autre, tu mets à plus forte raison une infinité de degrés dans le souverain bien, ce bien au-dessus duquel je ne conçois aucun degré. Qu’un homme soit moins heureux qu’un autre, naturellement il ambitionnera cette vie plus heureuse que la sienne. Or l’homme vraiment heureux ne préfère rien à son sort. Il est de même peu croyable qu’il reste quelque chose que le sage aime mieux être que ce qu’il est, ou qu’il ne préfère pas ce qui serait meilleur à ce qu’il a. Car assurément, plus il sera sage, plus il se portera vivement vers la meilleure des situations et voudra la conquérir à tout prix. Or comment serait heureux l’homme qui peut encore, que dis-je ? qui doit encore désirer quelque chose ? Je vais dire d’où vient cette erreur : on ne sait point qu’il n’y a qu’une vie heureuse. Ce qui fait d’elle la meilleure situation possible, c’est sa qualité et non sa grandeur. Aussi est-elle la même, longue ou courte, répandue ou concentrée, qu’elle se partage entre une infinité de lieux et de devoirs, ou qu’elle se replie sur un seul objet. L’estimer par nombre, mesure et parties, c’est lui ôter son excellence. Or qu’y a-t-il d’excellent en elle ? qu’elle est une vie pleine. Le terme du manger comme du boire est, je pense, la satiété. L’un a mangé plus, l’autre moins ; qu’importe ? les voilà tous deux rassasiés. L’un boit davantage, l’autre moins ; qu’importe, si tous deux n’ont plus soif ? Celui-ci a vécu plus d’années que celui-là : il n’importe, si les nombreuses années du premier n’ont point comporté plus de bonheur que le peu d’années du second. L’homme dont tu dis : « Il est moins heureux, » ne l’est pas du tout ; ce titre d’heureux n’admet pas de diminutif. « Qui est courageux est sans crainte ; qui est sans crainte est sans tristesse ; qui est sans tristesse est heureux. » Ce syllogisme est de notre école. On cherche à répondre à cela : que nous nous emparons d’un fait erroné et contestable comme d’une chose avouée, en disant que l’homme courageux est sans crainte. Car enfin, cet homme ne craindra-t-il pas des maux imminents ? Ne pas les craindre serait pure folie, aliénation d’esprit plutôt que courage. Il craindra, sans doute très-légèrement ; mais il ne sera pas tout à fait hors de crainte. « Parler ainsi, c’est toujours retomber dans l’abus de prendre pour vertus des vices moindres. Car celui qui craint, quoique plus rarement et moins que d’autres, n’est point pur des atteintes du mal ; seulement elles sont plus légères. » – Encore une fois, je tiens pour insensé quiconque n’appréhende pas tout mal imminent. – « Vous dites vrai, si c’est un mal ; mais s’il sait que ce n’en est point un, s’il ne juge comme mal que la turpitude, il devra envisager le péril d’un œil calme et dédaigner ce que d’autres peuvent craindre ; ou bien, s’il est d’un fou et d’un homme hors de sens de ne pas avoir peur du mal, plus on sera sage, plus cette peur sera forte. » À votre sens, l’homme courageux se jettera donc au-devant des dangers ? « Point du tout. Il ne les craindra pas, mais il les évitera : la prudence lui sied, si la crainte ne lui sied point. » Eh quoi ! la mort, les fers, les brasiers, tous les traits de la Fortune ne l’effrayeront pas ? « Non : il sait que ce ne sont point des maux, mais des semblants de maux ; il voit dans tout cela des épouvantails. Représente-lui la captivité, les fouets sanglants, les chaînes, l’indigence et ces membres que déchirent la maladie ou les cruautés des hommes, évoque d’autres fléaux encore, il les comptera parmi les terreurs paniques. C’est aux peureux à en avoir peur. Regardes-tu comme mal ce à quoi l’homme doit souvent se porter de lui-même ? » Tu demandes : Qu’est-ce que le mal ? C’est de céder à ce qu’on appelle maux, et de livrer lâchement cette indépendance pour laquelle il faut tout souffrir. C’en est fait de l’indépendance, si on ne brave les vaines menaces qui nous imposent leur joug. On ne mettrait pas en problème ce qui convient à l’homme courageux, si l’on savait ce que c’est que courage. Ce n’est point témérité irréfléchie ni amour des périls, ni manie de rechercher ce que tous redoutent ; c’est la science de distinguer ce qui est mal et ce qui ne l’est pas. Le courage n’excelle pas moins à se protéger lui-même qu’à supporter ces choses qui ont une fausse apparence de maux. « Mais enfin, si le fer est levé sur la tête de l’homme courageux ou va creusant tour à tour telle et telle partie de son corps ; s’il voit rouler sur ses genoux ses entrailles ; si par intervalles, pour qu’il sente mieux ses tortures, on revient à la charge ; si de ses viscères, de ses plaies ressuyées on tire encore de nouveau sang<small><sup>10</sup></small>, n’éprouve-t-il, dis-moi, ni crainte ni douleur ? » Il souffre sans doute, car le plus grand courage ne dépouille point la sensation physique ; mais il ne craint pas, il n’est pas vaincu, il regarde d’en haut ses souffrances. Veux-tu savoir quel esprit l’anime ? Celui d’un ami exhortant son ami malade. « Ce qui est un mal est nuisible ; ce qui nuit fait que l’homme vaut moins : ni la douleur, ni la pauvreté n’altèrent ses mérites ; donc elles ne sont point des maux. » Cette proposition, nous dit-on, est fausse ; car il y a telle chose qui peut nuire à l’homme sans qu’il en vaille moins. La tempête et les mauvais temps nuisent au pilote, et ne lui ôtent rien de son talent. Certains stoïciens répondent que le talent du pilote se perd dans la tempête et le mauvais temps en ce qu’il ne peut plus accomplir ce qu’il se propose et suivre sa direction : il tombe au-dessous non point de son art, mais de son œuvre. Sur quoi le péripatéticien : « Voilà donc aussi le sage qui vaut moins si la pauvreté, si la douleur, si d’autres crises semblables le pressent : elles ne lui ôtent pas sa vertu, elles en empêchent l’action. » L’objection serait juste, s’il n’y avait disparité entre le pilote et le sage. Celui-ci se propose, dans la conduite de sa vie, non d’accomplir quoi qu’il arrive ce qu’il entreprend, mais d’agir en tout selon le devoir ; le but du pilote est de vaincre tous les obstacles pour mener son navire au port. Les arts ne sont que des agents : ils doivent tenir ce qu’ils promettent ; la sagesse commande et dirige. Les arts sont les serviteurs de la vie ; la sagesse en est la souveraine. Il y a une autre réponse à faire, ce me semble ; savoir : que jamais ni l’art du pilote ne perd à la tempête, ni l’application de cet art. Le pilote ne te promet point une heureuse traversée : il te promet ses utiles services, son habileté à conduire un vaisseau, laquelle brille d’autant plus que des contre-temps fortuits lui suscitent plus d’obstacles. Celui qui peut dire : « Neptune, jamais tu n’engloutiras ce vaisseau sans que je tienne mon gouvernail droit<small><sup>11</sup></small><nowiki>, » a satisfait à l’art ; ce n’est pas l’œuvre du pilote, c’est le succès que compromet la tempête. « Comment ? il ne nuit pas au pilote l’accident qui l’empêche de gagner le port, qui rend ses efforts impuissants, qui le reporte en arrière ou le tient immobile, ou enlève ses agrès ? » Ce n’est pas comme pilote, c’est comme navigateur qu’il en souffre. Loin que cela déconcerte son art, il en ressort davantage : car en temps calme, comme on dit, le premier venu est pilote. Le gros temps fait tort au navire, non au pilote en tant que pilote. Il y a en lui deux personnes : l’une qui lui est commune avec tous ceux qui montent le bâtiment où lui-même compte comme passager ; l’autre qui lui est propre et qui le constitue pilote. La tempête lui nuit sous le premier rapport, non pas sous le second. Et puis son art existe pour le service d’autrui : ce sont les passagers qu’il intéresse, comme l’art du médecin s’applique à ceux qu’il traite. La sagesse est un bien tout à la fois commun aux hommes avec lesquels vit le sage, et personnel au sage. Ainsi peut-être la tempête contrarie le pilote en paralysant le ministère qu’il a promis aux passagers ; mais le sage ne reçoit d’échec ni de la pauvreté, ni de la douleur, ni d’aucun des orages de la vie ; car ils n’enchaînent point tous ses actes, mais seulement ceux qui touchent ses semblables : lui-même agit toujours sans toujours réussir[4], et n’est jamais plus grand que quand le sort lui fait obstacle : il remplit alors la vraie mission de la sagesse, qui est le bien, avons-nous dit, et des autres hommes et du sage.</nowiki> <nowiki>Mais de plus, il ne tombe même pas dans l’impuissance de les servir, lorsque pour son compte il est victime de quelque fatalité. L’humilité de sa fortune l’empêche-t-elle d’enseigner d’exemple l’art de gouverner les peuples, il enseignera comment se gouverne la pauvreté ; son œuvre s’étend à toutes les circonstances de la vie. Et il n’y a ni condition, ni événement qui exclue son action : il remplit alors ce même rôle qui lui interdit de remplir les autres. Également propre à toutes deux, la bonne fortune il la réglera, la mauvaise il la vaincra. Il a exercé sa vertu de manière à la déployer dans les revers comme dans le succès, à n’envisager qu’elle, non la matière qu’elle doit mettre en œuvre. Voilà pourquoi ni pauvreté, ni douleur, ni rien de ce qui pousse les esprits ignorants hors de la voie et dans l’abîme n’arrête le sage. Tu crois que le malheur l’accable ? Le malheur lui sert. Ce n’était pas d’ivoire seulement que Phidias savait faire des statues ; il en faisait de bronze. Tu lui aurais donné du marbre, ou toute autre matière vile au prix du marbre, qu’il en eût tiré, selon qu’elle s’y fût prêtée, des chefs-d’œuvre. Ainsi le sage signalera sa vertu, s’il le peut, dans la richesse ; faute de mieux, dans la pauvreté ; dans sa patrie, s’il y habite ; sinon, sur la terre d’exil ; comme général ou comme soldat, en santé comme en maladie. Quelque destinée qui lui advienne, il en fera sortir de mémorables résultats. Certains hommes domptent les bêtes sauvages et soumettent au joug les plus féroces, celles dont la rencontre nous glace de terreur. C’est peu qu’ils les dépouillent de leur caractère farouche, ils les apprivoisent jusqu’à la familiarité. Le lion souffre de son maître qu’il porte la main dans sa gueule ; le tigre se laisse embrasser de son gardien ; un nain d’Éthiopie fait mettre à genoux et marcher sur la corde un éléphant[5]. De même le sage est expert dans l’art de dompter les maux. La douleur, l’indigence, l’ignominie, la captivité, l’exil, monstres affreux partout ailleurs, dès qu’ils approchent ne sont plus intraitables.</nowiki> LETTRE LXXXV. 10. M. de Maistre a cru voir là, comme dans la ''Lettre'' lxxviii, un souvenir des tortures infligées sous Néron aux chrétiens ; et il a remarqué une imitation évidente de Sénèque par Lactance, qui dit à propos des martyrs de Dioclétien : « La seule chose qu’évitent les bourreaux, c’est que les chrétiens ne meurent dans la torture ; ils ont grand soin que leurs membres reprennent de la force pour d’autres souffrances, et puissent fournir de nouveau sang au supplice. » (''Divin. Instit.'' , V, ii.) 11. Voir Consol. à Marcia, vi. « Que la fortune heurte votre vaisseau par tous les endroits et le couvre de toutes ses vagues, elle ne vous empêchera pas de tenir le gouvernail droit. (Balzac, ''Consol. à M. de La Valette.'') # Aller↑ ''Énéid.'', VII, 808. Barthélémy. # Aller↑ Voir ''De la colère'', I, vii et viii. # Aller↑ Au texte: ''cum causis crescent, tantique erunt quanti'' (ou ''quanto'') ''fient''; ce qui n'offre pas de sens. J'ai lu ''quantæ''. # Aller↑ Je lis avec deux Mss. ''non in effectu.'' Lemaire: ''et in effectu.'' # Aller↑ Voy. Suét., ''Galba'', vi, et Pline, ''Hist. nat.'', VIII, ii. <span itemprop="associatedMedia" itemscope="" itemtype="http://schema.org/MediaObject"></span><span id="20._.E2.80.94_DE_Mme_DE_S.C3.89VIGN.C3.89_.C3.80_M._DE_COULANGES."></span><span class="pagenum ws-pagenum" id="76" title="Page:Sévigné - Lettres choisies, Didot, 1846.djvu/84"></span> [[Catégorie:Philosophie]] [[Catégorie:Commentaire philosophique]] __FORCERSOMMAIRE__ <references /> = Le tyran ignore la morale, l'amitié et le bonheur. La Boétie = [[Étude d'un texte de la Boétie]] [[Catégorie:Livres avec version PDF]] [[Catégorie:Versions imprimables]] 7hxdku5mccfwttg1h90otlqrl1syivm Utilisateur:Lionel Scheepmans/Brouillon 2 77615 765937 765437 2026-05-04T13:04:45Z Lionel Scheepmans 20012 765937 wikitext text/x-wiki Voir la [[w:en:list_of_wikis|liste de tous les wikis :]] 17zictpuw2wwe7cgi75t0qoe5eqgvyv Python pour le calcul scientifique/Algèbre linéaire 0 78137 765942 756609 2026-05-04T13:48:09Z Cdang 1202 /* Opérations vectorielles */ typo 765942 wikitext text/x-wiki Rappelons que dorénavant les programmes commencent tous par : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> L'algèbre linéaire consiste essentiellement en la manipulation de matrices. {{loupe|../Manipulation de matrices}} == Modules à utiliser == Deux modules fournissent les fonctions utiles pour l'algèbre linéaire : <code lang="python">numpy.linalg</code> et <code lang="python">scipy.linalg</code>. Le module SciPy est plus complet et est parfois plus rapide que le module NumPy. Nous considérons donc que nous utilisons le premier et ajoutons en en-tête : <syntaxhighlight lang="python"> from scipy import linalg </syntaxhighlight> == Opérations vectorielles == Un vecteur ''a'' de composantes (''a''<sub>1</sub>, ''a''<sub>2</sub>, ''a''<sub>3</sub>) se définit par : <code>a = np.matrix([a1, a2, a3])</code>. La somme de deux vecteurs ''a'' + ''b'' s'obtient par : <code>a + b</code>. Le produit scalaire réel ''a'' ⋅ ''b'' s'obtient par : <code>a @ b</code> ou bien <code>a.dot(b)</code>. Pour un espace vectoriel complexe, le produit hermitien ''a'' ⋅ ''b'' = ∑{{surligner|''a''}}<sub>''i''</sub> ⋅ ''b''<sub>''i''</sub> s'obtient par : <code>np.vecdot(a, b)</code>. Le produit vectoriel ''a'' ∧ ''b'' (ou ''a'' × ''b'') s'obtient par : <code>np.cross(a, b)</code>. == Opérations matricielles == Une matrice : <math>\mathrm{M} = \begin{pmatrix} m_{11} & m_{12} & m_{13} \\ m_{21} & m_{22} & m_{23} \\ m_{31} & m_{32} & m_{33} \end{pmatrix}</math> se définit par : <code>M = np.matrix([[m11, m12, m13], [m21, m22, m23], [m31, m32, m33]])</code>. La somme de deux matrices A + B s'obtient par <code>A + B</code>. Le produit ''a'' ⋅ M d'une matrice M par un scalaire ''a'' s'obtient par <code>a * M</code> ou bien <code>np.multiply(a, M)</code>. Le produit matriciel A ⋅ B s'obtient par <code>A @ B</code> ou bien <code>np.matmult(A, B)</code>. == Norme et déterminant == La norme « classique » s'obtient par <code lang="python">linalg.norm()</code> ; pour les vecteurs, il s'agit de la norme d'ordre 2 (L2) et pour les matrice, la norme de Frobénius <syntaxhighlight lang="python"> A = np.array([[1,2],[3,4]]) linalg.norm(A) # 5.4772... </syntaxhighlight> Pour les vecteurs, on peut utiliser une norme d'ordre ''n'' quelconque, <math>\| x \| = \left ( \sum_i | x_i |^n \right ) ^{1/n}</math> avec <syntaxhighlight lang="python"> linalg.norm(x, n) </syntaxhighlight> Si ''n'' prend pour valeur <code lang="python">inf</code>, cela renvoie max(|''x<sub>i</sub>''|) ; pour valeur <code lang="python">-inf</code>, min(|''x<sub>i</sub>''|). Pour une matrice, les valeurs d'ordre possibles sont <code lang="python">±1</code>, <code lang="python">±2</code>, <code lang="python">±inf</code> et <code lang="python">"fro"</code> (pour Frobenius, valeur par défaut)<ref>voir {{lien web |url=https://scipy.github.io/devdocs/tutorial/linalg.html#computing-norms |titre=Computing norms |site=SciPy.org | lang=en |consulté le=2022-05-03}}.</ref>. Si la matrice est un assemblage de vecteurs, alors on peut calculer la norme de chaque vecteur en indiquant le numéro de l'indice (l'axe) selon lequel on calcule la norme. Par exemple, si on a une matrice de vecteurs colonne M = [[X<sub>1</sub>, X<sub>2</sub>, …], [Y<sub>1</sub>, Y<sub>2</sub>, …]], alors <code lang="python">linalg.norm(M, axis=0)</code> va calculer la norme selon l'indice 0 (le numéro de ligne) : [|(X<sub>1</sub>, Y<sub>1</sub>)|, |(X<sub>2</sub>, Y<sub>2</sub>)|, …]. Le déterminant s'obtient avec <syntaxhighlight lang="python"> linalg.det(A) </syntaxhighlight> == Résolution d'un système linéaire == Un système linéaire d'équations peut se représenter par une équation matricielle : : <math> \begin{cases} a_1\cdot x + b_1\cdot y = _c1 \\ a_2\cdot x + b_2\cdot y = c_2 \\ \end{cases} \Leftrightarrow \mathrm{A} \cdot \mathrm{X} = b </math> avec : <math> \mathrm{A} = \begin{Bmatrix} a_1 & b_1 \\ a_2 & b_2 \end{Bmatrix} ; \mathrm{X} = \begin{Bmatrix} x \\ y \end{Bmatrix} ; b = \begin{Bmatrix} c_1 \\ c_2 \end{Bmatrix} </math> Si le système possède une solution, alors la matrice A est inversible et le résultat peut s'obtenir par : : <math> \mathrm{X} = \mathrm{A}^{-1} \cdot b </math> En Python, cela peut s'obtenir de deux manières : * <code lang="python">X = linalg.inv(A).dot(b)</code> ; * <code lang="python">X = linalg.solve(A, b)</code> ; la seconde méthode étant plus rapide. Par exemple, pour résoudre le système : <math> \begin{cases} x + 2\cdot y = 5 \\ 3\cdot x + 4\cdot y = 6 \\ \end{cases} </math> <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) b = np.array([[5], [6]]) X = linalg.solve(A, b) print(X) # [[-4. ] # [ 4.5]] </syntaxhighlight> == Notes et références == {{références}} ---- [[../Régression et optimisation|Régression et optimisation]] &lt; [[../|↑]] &gt; [[../Calcul différentiel et intégral|Calcul différentiel et intégral]] {{DEFAULTSORT:Algebre lineaire}} [[Catégorie:Python pour le calcul scientifique (livre)]] h868zofhdglj6pv3b86l2fmcmg4721o Fonctionnement d'un ordinateur/Version imprimable 2 0 78962 765985 747859 2026-05-04T20:39:31Z Mewtow 31375 765985 wikitext text/x-wiki __NOTOC__ {{:Fonctionnement d'un ordinateur/Introduction}} =Le codage des informations= {{:Fonctionnement d'un ordinateur/L'encodage des données}} {{:Fonctionnement d'un ordinateur/Le codage des nombres}} {{:Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur}} =Les circuits électroniques= {{:Fonctionnement d'un ordinateur/Les portes logiques}} ==Les circuits combinatoires== {{:Fonctionnement d'un ordinateur/Les circuits combinatoires}} {{:Fonctionnement d'un ordinateur/Les circuits de masquage}} {{:Fonctionnement d'un ordinateur/Les circuits de sélection}} ==Les circuits séquentiels== {{:Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit}} {{:Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones}} {{:Fonctionnement d'un ordinateur/Les registres et mémoires adressables}} {{:Fonctionnement d'un ordinateur/Les circuits compteurs et décompteurs}} {{:Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence}} ==Les circuits de calcul et de comparaison== {{:Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation}} {{:Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction}} {{:Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit}} {{:Fonctionnement d'un ordinateur/Les circuits de comparaison}} {{:Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande}} {{:Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul flottant}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques}} ==Les circuits intégrés== {{:Fonctionnement d'un ordinateur/Les transistors et portes logiques}} {{:Fonctionnement d'un ordinateur/Les circuits intégrés}} {{:Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus}} =L'architecture d'un ordinateur= {{:Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur}} {{:Fonctionnement d'un ordinateur/La hiérarchie mémoire}} {{:Fonctionnement d'un ordinateur/La performance d'un ordinateur}} {{:Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques}} {{:Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur}} =Les bus et liaisons point à point= {{:Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS}} {{:Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)}} {{:Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus}} {{:Fonctionnement d'un ordinateur/Les liaisons point à point}} {{:Fonctionnement d'un ordinateur/Les bus électroniques}} {{:Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point}} =Les mémoires= {{:Fonctionnement d'un ordinateur/Les différents types de mémoires}} {{:Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique}} {{:Fonctionnement d'un ordinateur/Le bus mémoire}} ==La micro-architecture d'une mémoire adressable== {{:Fonctionnement d'un ordinateur/Les cellules mémoires}} {{:Fonctionnement d'un ordinateur/Le plan mémoire}} {{:Fonctionnement d'un ordinateur/Contrôleur mémoire interne}} {{:Fonctionnement d'un ordinateur/Mémoires évoluées}} ==Les mémoires primaires== {{:Fonctionnement d'un ordinateur/Les mémoires ROM}} {{:Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones}} {{:Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)}} {{:Fonctionnement d'un ordinateur/Contrôleur mémoire externe}} ==Les mémoires exotiques== {{:Fonctionnement d'un ordinateur/Les mémoires associatives}} {{:Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO}} =Le processeur= ==L'architecture externe== {{:Fonctionnement d'un ordinateur/Langage machine et assembleur}} {{:Fonctionnement d'un ordinateur/Les registres du processeur}} {{:Fonctionnement d'un ordinateur/Les modes d'adressage}} {{:Fonctionnement d'un ordinateur/L'encodage des instructions}} {{:Fonctionnement d'un ordinateur/Les jeux d'instructions}} {{:Fonctionnement d'un ordinateur/La pile d'appel et les fonctions}} {{:Fonctionnement d'un ordinateur/Les interruptions et exceptions}} {{:Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme}} ==La micro-architecture== {{:Fonctionnement d'un ordinateur/Les composants d'un processeur}} {{:Fonctionnement d'un ordinateur/Le chemin de données}} {{:Fonctionnement d'un ordinateur/L'unité de chargement et le program counter}} {{:Fonctionnement d'un ordinateur/L'unité de contrôle}} {{:Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements}} ==Les jeux d’instructions spécialisés== {{:Fonctionnement d'un ordinateur/Les architectures à accumulateur}} {{:Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins}} {{:Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire}} ==La mémoire virtuelle et la protection mémoire== {{:Fonctionnement d'un ordinateur/L'espace d'adressage du processeur}} {{:Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle}} =Les entrées-sorties et périphériques= {{:Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques}} {{:Fonctionnement d'un ordinateur/L'adressage des périphériques}} {{:Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension}} =Les mémoires de masse= {{:Fonctionnement d'un ordinateur/Les mémoires de masse : généralités}} {{:Fonctionnement d'un ordinateur/Les disques durs}} {{:Fonctionnement d'un ordinateur/Les solid-state drives}} {{:Fonctionnement d'un ordinateur/Les disques optiques}} {{:Fonctionnement d'un ordinateur/Les technologies RAID}} =La mémoire cache= {{:Fonctionnement d'un ordinateur/Les mémoires cache}} {{:Fonctionnement d'un ordinateur/Le préchargement}} {{:Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer}} =Le parallélisme d’instructions= {{:Fonctionnement d'un ordinateur/Le pipeline}} ==Les branchements et le front-end== {{:Fonctionnement d'un ordinateur/La prédiction de branchement}} {{:Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions}} ==L’exécution dans le désordre== {{:Fonctionnement d'un ordinateur/Les pipelines multicycles}} {{:Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions}} {{:Fonctionnement d'un ordinateur/Le contournement (data forwarding)}} {{:Fonctionnement d'un ordinateur/L'exécution dans le désordre}} {{:Fonctionnement d'un ordinateur/Le renommage de registres}} {{:Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo}} {{:Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans le désordre}} {{:Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache}} ==L'émission multiple== {{:Fonctionnement d'un ordinateur/Les processeurs superscalaires}} {{:Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86}} {{:Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC}} {{:Fonctionnement d'un ordinateur/Les architectures dataflow}} =Les architectures parallèles= {{:Fonctionnement d'un ordinateur/Les architectures parallèles}} {{:Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs}} {{:Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading}} {{:Fonctionnement d'un ordinateur/Les architectures à parallélisme de données}} {{:Fonctionnement d'un ordinateur/La cohérence des caches}} {{:Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire}} =Annexes= ==Les nombres flottants : FPUs et coprocesseurs== {{:Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87}} {{:Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO}} ==Les jeux d’instruction spécialisés== {{:Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation}} {{:Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation}} {{:Fonctionnement d'un ordinateur/Les processeurs de traitement du signal}} {{:Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement}} {{:Fonctionnement d'un ordinateur/Les architectures systoliques}} ==Les autres annexes== {{:Fonctionnement d'un ordinateur/Les réseaux de neurones matériels}} {{:Fonctionnement d'un ordinateur/Le matériel réseau}} {{:Fonctionnement d'un ordinateur/La tolérance aux pannes}} {{:Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires}} {{:Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires}} {{:Fonctionnement d'un ordinateur/Les circuits réversibles}} {{:Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique}} {{autocat}} 5i6rvx44fiaurpbsbcr5iwtqt8rhvwm 765986 765985 2026-05-04T20:40:36Z Mewtow 31375 765986 wikitext text/x-wiki __NOTOC__ {{:Fonctionnement d'un ordinateur/Introduction}} =Le codage des informations= {{:Fonctionnement d'un ordinateur/L'encodage des données}} {{:Fonctionnement d'un ordinateur/Le codage des nombres}} {{:Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur}} =Les circuits électroniques= {{:Fonctionnement d'un ordinateur/Les portes logiques}} ==Les circuits combinatoires== {{:Fonctionnement d'un ordinateur/Les circuits combinatoires}} {{:Fonctionnement d'un ordinateur/Les circuits de masquage}} {{:Fonctionnement d'un ordinateur/Les circuits de sélection}} ==Les circuits séquentiels== {{:Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit}} {{:Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones}} {{:Fonctionnement d'un ordinateur/Les registres et mémoires adressables}} {{:Fonctionnement d'un ordinateur/Les circuits compteurs et décompteurs}} {{:Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence}} ==Les circuits de calcul et de comparaison== {{:Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation}} {{:Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction}} {{:Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit}} {{:Fonctionnement d'un ordinateur/Les circuits de comparaison}} {{:Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande}} {{:Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul flottant}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques}} ==Les circuits intégrés== {{:Fonctionnement d'un ordinateur/Les transistors et portes logiques}} {{:Fonctionnement d'un ordinateur/Les circuits intégrés}} {{:Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus}} =L'architecture d'un ordinateur= {{:Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur}} {{:Fonctionnement d'un ordinateur/La hiérarchie mémoire}} {{:Fonctionnement d'un ordinateur/La performance d'un ordinateur}} {{:Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques}} {{:Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur}} =Les bus et liaisons point à point= {{:Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS}} {{:Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)}} {{:Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus}} {{:Fonctionnement d'un ordinateur/Les liaisons point à point}} {{:Fonctionnement d'un ordinateur/Les bus électroniques}} {{:Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point}} =Les mémoires= {{:Fonctionnement d'un ordinateur/Les différents types de mémoires}} {{:Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique}} {{:Fonctionnement d'un ordinateur/Le bus mémoire}} ==La micro-architecture d'une mémoire adressable== {{:Fonctionnement d'un ordinateur/Les cellules mémoires}} {{:Fonctionnement d'un ordinateur/Le plan mémoire}} {{:Fonctionnement d'un ordinateur/Contrôleur mémoire interne}} {{:Fonctionnement d'un ordinateur/Mémoires évoluées}} ==Les mémoires primaires== {{:Fonctionnement d'un ordinateur/Les mémoires ROM}} {{:Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones}} {{:Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)}} {{:Fonctionnement d'un ordinateur/Contrôleur mémoire externe}} ==Les mémoires exotiques== {{:Fonctionnement d'un ordinateur/Les mémoires associatives}} {{:Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO}} =Le processeur= ==L'architecture externe== {{:Fonctionnement d'un ordinateur/Langage machine et assembleur}} {{:Fonctionnement d'un ordinateur/Les registres du processeur}} {{:Fonctionnement d'un ordinateur/Les modes d'adressage}} {{:Fonctionnement d'un ordinateur/L'encodage des instructions}} {{:Fonctionnement d'un ordinateur/Les jeux d'instructions}} {{:Fonctionnement d'un ordinateur/La pile d'appel et les fonctions}} {{:Fonctionnement d'un ordinateur/Les interruptions et exceptions}} {{:Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme}} ==La micro-architecture== {{:Fonctionnement d'un ordinateur/Les composants d'un processeur}} {{:Fonctionnement d'un ordinateur/Le chemin de données}} {{:Fonctionnement d'un ordinateur/L'unité de chargement et le program counter}} {{:Fonctionnement d'un ordinateur/L'unité de contrôle}} {{:Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements}} ==Les jeux d’instructions spécialisés== {{:Fonctionnement d'un ordinateur/Les architectures à accumulateur}} {{:Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins}} {{:Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire}} ==La mémoire virtuelle et la protection mémoire== {{:Fonctionnement d'un ordinateur/L'espace d'adressage du processeur}} {{:Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle}} =Les entrées-sorties et périphériques= {{:Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques}} {{:Fonctionnement d'un ordinateur/L'adressage des périphériques}} {{:Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension}} =Les mémoires de masse= {{:Fonctionnement d'un ordinateur/Les mémoires de masse : généralités}} {{:Fonctionnement d'un ordinateur/Les disques durs}} {{:Fonctionnement d'un ordinateur/Les solid-state drives}} {{:Fonctionnement d'un ordinateur/Les disques optiques}} {{:Fonctionnement d'un ordinateur/Les technologies RAID}} =La mémoire cache= {{:Fonctionnement d'un ordinateur/Les mémoires cache}} {{:Fonctionnement d'un ordinateur/Le préchargement}} {{:Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer}} =Le parallélisme d’instructions= {{:Fonctionnement d'un ordinateur/Le pipeline}} ==Les branchements et le front-end== {{:Fonctionnement d'un ordinateur/La prédiction de branchement}} {{:Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions}} ==Les pipelines multicycles simples== {{:Fonctionnement d'un ordinateur/Les pipelines multicycles}} {{:Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions}} {{:Fonctionnement d'un ordinateur/Le contournement (data forwarding)}} {{:Fonctionnement d'un ordinateur/Les premiers processeurs Intel}} ==L’exécution dans le désordre== {{:Fonctionnement d'un ordinateur/L'exécution dans le désordre}} {{:Fonctionnement d'un ordinateur/Le renommage de registres}} {{:Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo}} {{:Fonctionnement d'un ordinateur/Les unités mémoires à exécution dans le désordre}} {{:Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache}} ==L'émission multiple== {{:Fonctionnement d'un ordinateur/Les processeurs superscalaires}} {{:Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86}} {{:Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC}} {{:Fonctionnement d'un ordinateur/Les architectures dataflow}} =Les architectures parallèles= {{:Fonctionnement d'un ordinateur/Les architectures parallèles}} {{:Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs}} {{:Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading}} {{:Fonctionnement d'un ordinateur/Les architectures à parallélisme de données}} {{:Fonctionnement d'un ordinateur/La cohérence des caches}} {{:Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire}} =Annexes= ==Les nombres flottants : FPUs et coprocesseurs== {{:Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87}} {{:Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO}} ==Les jeux d’instruction spécialisés== {{:Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation}} {{:Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation}} {{:Fonctionnement d'un ordinateur/Les processeurs de traitement du signal}} {{:Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement}} {{:Fonctionnement d'un ordinateur/Les architectures systoliques}} ==Les autres annexes== {{:Fonctionnement d'un ordinateur/Les réseaux de neurones matériels}} {{:Fonctionnement d'un ordinateur/Le matériel réseau}} {{:Fonctionnement d'un ordinateur/La tolérance aux pannes}} {{:Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires}} {{:Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires}} {{:Fonctionnement d'un ordinateur/Les circuits réversibles}} {{:Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique}} {{autocat}} 6ps3n58g60ppha0jjkvf1am13acorxq 765987 765986 2026-05-04T20:41:25Z Mewtow 31375 765987 wikitext text/x-wiki __NOTOC__ {{:Fonctionnement d'un ordinateur/Introduction}} =Le codage des informations= {{:Fonctionnement d'un ordinateur/L'encodage des données}} {{:Fonctionnement d'un ordinateur/Le codage des nombres}} {{:Fonctionnement d'un ordinateur/Les codes de détection/correction d'erreur}} =Les circuits électroniques= {{:Fonctionnement d'un ordinateur/Les portes logiques}} ==Les circuits combinatoires== {{:Fonctionnement d'un ordinateur/Les circuits combinatoires}} {{:Fonctionnement d'un ordinateur/Les circuits de masquage}} {{:Fonctionnement d'un ordinateur/Les circuits de sélection}} ==Les circuits séquentiels== {{:Fonctionnement d'un ordinateur/Les bascules : des mémoires de 1 bit}} {{:Fonctionnement d'un ordinateur/Les circuits synchrones et asynchrones}} {{:Fonctionnement d'un ordinateur/Les registres et mémoires adressables}} {{:Fonctionnement d'un ordinateur/Les circuits compteurs et décompteurs}} {{:Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence}} ==Les circuits de calcul et de comparaison== {{:Fonctionnement d'un ordinateur/Les circuits de décalage et de rotation}} {{:Fonctionnement d'un ordinateur/Les circuits pour l'addition et la soustraction}} {{:Fonctionnement d'un ordinateur/Les unités arithmétiques et logiques entières (simples)}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul logique et bit à bit}} {{:Fonctionnement d'un ordinateur/Les circuits de comparaison}} {{:Fonctionnement d'un ordinateur/Les circuits pour l'addition multiopérande}} {{:Fonctionnement d'un ordinateur/Les circuits pour la multiplication et la division}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul flottant}} {{:Fonctionnement d'un ordinateur/Les circuits de calcul trigonométriques}} ==Les circuits intégrés== {{:Fonctionnement d'un ordinateur/Les transistors et portes logiques}} {{:Fonctionnement d'un ordinateur/Les circuits intégrés}} {{:Fonctionnement d'un ordinateur/L'interface électrique entre circuits intégrés et bus}} =L'architecture d'un ordinateur= {{:Fonctionnement d'un ordinateur/L'architecture de base d'un ordinateur}} {{:Fonctionnement d'un ordinateur/La hiérarchie mémoire}} {{:Fonctionnement d'un ordinateur/La performance d'un ordinateur}} {{:Fonctionnement d'un ordinateur/La loi de Moore et les tendances technologiques}} {{:Fonctionnement d'un ordinateur/Les techniques de réduction de la consommation électrique d'un processeur}} =Les bus et liaisons point à point= {{:Fonctionnement d'un ordinateur/La carte mère, chipset et BIOS}} {{:Fonctionnement d'un ordinateur/Les bus et liaisons point à point (généralités)}} {{:Fonctionnement d'un ordinateur/Les encodages spécifiques aux bus}} {{:Fonctionnement d'un ordinateur/Les liaisons point à point}} {{:Fonctionnement d'un ordinateur/Les bus électroniques}} {{:Fonctionnement d'un ordinateur/Quelques exemples de bus et de liaisons point à point}} =Les mémoires= {{:Fonctionnement d'un ordinateur/Les différents types de mémoires}} {{:Fonctionnement d'un ordinateur/L'interface d'une mémoire électronique}} {{:Fonctionnement d'un ordinateur/Le bus mémoire}} ==La micro-architecture d'une mémoire adressable== {{:Fonctionnement d'un ordinateur/Les cellules mémoires}} {{:Fonctionnement d'un ordinateur/Le plan mémoire}} {{:Fonctionnement d'un ordinateur/Contrôleur mémoire interne}} {{:Fonctionnement d'un ordinateur/Mémoires évoluées}} ==Les mémoires primaires== {{:Fonctionnement d'un ordinateur/Les mémoires ROM}} {{:Fonctionnement d'un ordinateur/Les mémoires SRAM synchrones}} {{:Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM)}} {{:Fonctionnement d'un ordinateur/Contrôleur mémoire externe}} ==Les mémoires exotiques== {{:Fonctionnement d'un ordinateur/Les mémoires associatives}} {{:Fonctionnement d'un ordinateur/Les mémoires FIFO et LIFO}} =Le processeur= ==L'architecture externe== {{:Fonctionnement d'un ordinateur/Langage machine et assembleur}} {{:Fonctionnement d'un ordinateur/Les registres du processeur}} {{:Fonctionnement d'un ordinateur/Les modes d'adressage}} {{:Fonctionnement d'un ordinateur/L'encodage des instructions}} {{:Fonctionnement d'un ordinateur/Les jeux d'instructions}} {{:Fonctionnement d'un ordinateur/La pile d'appel et les fonctions}} {{:Fonctionnement d'un ordinateur/Les interruptions et exceptions}} {{:Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme}} ==La micro-architecture== {{:Fonctionnement d'un ordinateur/Les composants d'un processeur}} {{:Fonctionnement d'un ordinateur/Le chemin de données}} {{:Fonctionnement d'un ordinateur/L'unité de chargement et le program counter}} {{:Fonctionnement d'un ordinateur/L'unité de contrôle}} {{:Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements}} ==Les jeux d’instructions spécialisés== {{:Fonctionnement d'un ordinateur/Les architectures à accumulateur}} {{:Fonctionnement d'un ordinateur/Les processeurs 8 bits et moins}} {{:Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire}} ==La mémoire virtuelle et la protection mémoire== {{:Fonctionnement d'un ordinateur/L'espace d'adressage du processeur}} {{:Fonctionnement d'un ordinateur/L'abstraction mémoire et la mémoire virtuelle}} =Les entrées-sorties et périphériques= {{:Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques}} {{:Fonctionnement d'un ordinateur/L'adressage des périphériques}} {{:Fonctionnement d'un ordinateur/Les périphériques et les cartes d'extension}} =Les mémoires de masse= {{:Fonctionnement d'un ordinateur/Les mémoires de masse : généralités}} {{:Fonctionnement d'un ordinateur/Les disques durs}} {{:Fonctionnement d'un ordinateur/Les solid-state drives}} {{:Fonctionnement d'un ordinateur/Les disques optiques}} {{:Fonctionnement d'un ordinateur/Les technologies RAID}} =La mémoire cache= {{:Fonctionnement d'un ordinateur/Les mémoires cache}} {{:Fonctionnement d'un ordinateur/Le préchargement}} {{:Fonctionnement d'un ordinateur/Le Translation Lookaside Buffer}} =Le parallélisme d’instructions= {{:Fonctionnement d'un ordinateur/Le pipeline}} ==Les branchements et le front-end== {{:Fonctionnement d'un ordinateur/La prédiction de branchement}} {{:Fonctionnement d'un ordinateur/Les optimisations du chargement des instructions}} ==Les pipelines multicycles simples== {{:Fonctionnement d'un ordinateur/Les pipelines multicycles}} {{:Fonctionnement d'un ordinateur/L'émission dans l'ordre des instructions}} {{:Fonctionnement d'un ordinateur/Le contournement (data forwarding)}} {{:Fonctionnement d'un ordinateur/Les premiers processeurs Intel}} ==L’exécution dans le désordre== {{:Fonctionnement d'un ordinateur/L'exécution dans le désordre}} {{:Fonctionnement d'un ordinateur/Le renommage de registres}} {{:Fonctionnement d'un ordinateur/Le scoreboarding et l'algorithme de Tomasulo}} ==Les optimisations des accès mémoire== {{:Fonctionnement d'un ordinateur/La désambiguïsation mémoire}} {{:Fonctionnement d'un ordinateur/Les optimisations des accès mémoire}} ==L'émission multiple== {{:Fonctionnement d'un ordinateur/Les processeurs superscalaires}} {{:Fonctionnement d'un ordinateur/Exemples de microarchitectures CPU : le cas du x86}} {{:Fonctionnement d'un ordinateur/Les processeurs VLIW et EPIC}} {{:Fonctionnement d'un ordinateur/Les architectures dataflow}} =Les architectures parallèles= {{:Fonctionnement d'un ordinateur/Les architectures parallèles}} {{:Fonctionnement d'un ordinateur/Architectures multiprocesseurs et multicœurs}} {{:Fonctionnement d'un ordinateur/Architectures multithreadées et Hyperthreading}} {{:Fonctionnement d'un ordinateur/Les architectures à parallélisme de données}} {{:Fonctionnement d'un ordinateur/La cohérence des caches}} {{:Fonctionnement d'un ordinateur/Les sections critiques et le modèle mémoire}} =Annexes= ==Les nombres flottants : FPUs et coprocesseurs== {{:Fonctionnement d'un ordinateur/Un exemple de jeu d'instruction : l'extension x87}} {{:Fonctionnement d'un ordinateur/Les coprocesseurs : FPU et IO}} ==Les jeux d’instruction spécialisés== {{:Fonctionnement d'un ordinateur/L'accélération matérielle de la virtualisation}} {{:Fonctionnement d'un ordinateur/Les ISA optimisés pour la compilation/interprétation}} {{:Fonctionnement d'un ordinateur/Les processeurs de traitement du signal}} {{:Fonctionnement d'un ordinateur/Les architectures actionnées par déplacement}} {{:Fonctionnement d'un ordinateur/Les architectures systoliques}} ==Les autres annexes== {{:Fonctionnement d'un ordinateur/Les réseaux de neurones matériels}} {{:Fonctionnement d'un ordinateur/Le matériel réseau}} {{:Fonctionnement d'un ordinateur/La tolérance aux pannes}} {{:Fonctionnement d'un ordinateur/Les ordinateurs de première génération : tubes à vide et mémoires}} {{:Fonctionnement d'un ordinateur/Les ordinateurs à encodages non-binaires}} {{:Fonctionnement d'un ordinateur/Les circuits réversibles}} {{:Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique}} {{autocat}} qz2qbzfvtn8z6aak04ue03lx8kf9dhk Le mouvement Wikimédia/L'arrivée des projets frères 0 79273 765955 765665 2026-05-04T14:43:11Z Lionel Scheepmans 20012 765955 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Dans le but de développer des contenus pédagogiques qui ne trouvaient pas leur place dans Wikipédia, d’autres projets pédagogiques et collaboratifs ont vu le jour, pour former ce que l'on appelle couramment aujourd'hui : l’écosystème Wikimedia. La naissance de tous ces projets, ainsi que les évènements importants qui ont contribué au développement du mouvement, ont été repris dans une [[c:File:WikipediaTimeline.png|ligne du temps]] réalisée par [[m:user:Guillom|Guillaume Paumier]], à l’occasion du dixième anniversaire de Wikipédia. Grâce à ce graphique, qui complète avantageusement la page [[m:Wikimedia_News|''Wikimedia News'']], on peut découvrir en détail l'évolution des projets, des versions linguistiques, du nombre de contributeurs et d'articles, tout en observant le développement du mouvement dans son ensemble.[[Fichier:Wikimedia logo family complete-2022.svg|alt=Logo du mouvement Wikimédia entouré de 15 autres logos de projets actifs en son sein|vignette|<small>Figure 17. Logo de la Fondation Wikimédia entouré de 15 autres logos de projets actifs au sein du mouvement.</small>|300x300px|gauche]]Parmi tous les projets frères, le premier à apparaître fut Méta-Wiki, une plateforme de référence pour centraliser la gestion de l'ensemble des sites web hébergés par la fondation Wikimédia. Dans un premier temps, cet espace communautaire en ligne a répondu à la nécessité de traiter en un seul lieu les questions communes aux différentes versions linguistiques de Wikipédia. Aujourd'hui, le site web est le principal endroit de coordination et de gestion de l'ensemble du mouvement Wikimédia. On y retrouve énormément d'informations au sujet des projets en ligne, et peut-être plus encore, concernant la Fondation et les organismes affiliés. Après Méta-Wiki, sept autres projets de partage de la connaissance ont fait leur apparition, avant d'être déclinés à leur tour en plusieurs versions linguistiques. Tous ces projets émergent en général sur l’initiative d’un petit groupe de personnes actives au sein d’un projet préexistant. Ce fut le cas du projet Wiktionnaire en anglais, le deuxième projet à voir le jour après Méta-Wiki, en décembre 2002, soit deux ans avant la version francophone apparue en mars 2004<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire|url=https://web.archive.org/web/20200416091043/https://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire|consulté le=}}.</ref>. Il est intéressant d'observer que la version francophone du Wiktionnaire n’a pas été créée à partir du projet anglophone, mais bien depuis le projet Wikipédia en français. D'ailleurs, on peut retrouver dans les archives de ce dernier projet, un débat concernant la pertinence de cette création, dont voici un extrait. <blockquote> En fait, ce qui me peine vraiment avec le projet Wiktionary, c’est que alors qu’on essaie de rassembler les gens (pas facile) pour créer une sorte de tour de Babel de la connaissance (tâche bien longue et difficile), ce nouveau projet va disperser les énergies pour une raison qui ne me semble pas valable. C’est la création de Wiktionary qui va créer des redondances. À mon avis il existera rapidement des pages sur le même mot, mais ne contenant pas les mêmes informations. Pour quelle raison ces connaissances devraient-elles être séparées ? Les encyclopédies sur papier devaient faire des choix à cause du manque de place, mais nous, pourquoi le ferions-nous ??? "Wikipédia n’est pas un dictionnaire" n’est pas un argument à mon avis... si vraiment c’était pas un dictionnaire, il faudrait virer tout un tas d’article. Je ne comprends vraiment pas cette volonté de séparer la connaissance entre ce qui est "encyclopédique" et ce qui n’est "qu’une définition". [Réponse] Pour moi ce qu’est Wiktionary, c’est une partie de Wikipédia s’intéressant plus particulièrement aux aspects linguistiques des mots. La différence que je verrais entre la partie ''dictionnaire'' de Wikipédia et sa partie dite encyclopédique, c’est que la partie dictionnaire s’intéresserait au sens des mots eux-mêmes alors que la partie encyclopédie s’attache plus à faire ressortir un état des connaissances à un moment donné. Le pourquoi de la séparation d’avec la partie encyclopédie tient plus à des raisons techniques qu’à une volonté de monter un projet indépendant, en effet, à mon humble avis, un dictionnaire nécessite une plus grande rigueur (de présentation) qu’une encyclopédie. Ceci entraîne beaucoup de problème et entre autres le choix de la mise en forme des articles du dictionnaire.<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire/Discussion Wikipédia:Wiktionary|url=https://web.archive.org/web/20140831102908/http://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire/Discussion_Wikip%C3%A9dia:Wiktionary|date=|consulté le=}}.</ref> </blockquote> Créer un nouveau projet, c’est effectivement créer de nouveaux sites web, qui devront faire l’objet d’une nouvelle gestion, tant pour les serveurs de la Fondation, que pour la nouvelle communauté de contributeurs. L’importation de pages d’un projet à l'autre ou la traduction de celles-ci sont bien sûr toujours possibles, mais cela duplique alors aussi la maintenance et les mises à jour. Le choix de scinder un projet, en faveur d’une plus grande liberté, comporte donc certains coûts humains et financiers. Ce prix à payer n'a pour autant pas empêché le projet anglophone Wikibooks de faire son apparition le 10 juin 2003, soit près d’un an avant Wikilivres, la version francophone du projet, apparue le 22 juillet 2004. Cette dernière création avait de nouveau été débattue au sein de la communauté Wikipédia en français, et non pas dans le Wikibooks en anglais. Quant à l'objectif commun aux deux projets linguistiques, il était de créer une « bibliothèque de livres pédagogiques libres que chacun peut améliorer<ref>{{Lien web|auteur=Wikilivres|url=https://fr.wikibooks.org/w/index.php?title=Accueil&oldid=586825|titre=Acceuil|consulté le=}}.</ref> ». Environ un an après la création du projet en anglais, un nouvel [[w:fr:Espace de noms|espace de noms]] intitulé Wikijunior fut mis en place au sein de la bibliothèque en ligne. Ce sous-projet avait été créé pour répondre à un financement de la fondation ''[[w:en:Graham_Beck|Beck]],'' qui cherchait à promouvoir la production de nouvelles littératures pour des enfants de huit à onze ans<ref>{{Lien web|auteur=Méta-Wiki|titre=Wikijunior/proposal to Beck Foundation|url=https://web.archive.org/web/20150925041619/https://meta.wikimedia.org/wiki/Wikijunior/proposal_to_Beck_Foundation|consulté le=}}.</ref>. Peu de temps après, cette tranche d’âge fut toutefois élargie de zéro à douze ans au niveau du projet francophone, quand le sous-projet y fut adopté<ref>{{Lien web|auteur=Wikilivres|titre=Wikijunior|url=https://web.archive.org/web/20210414045051/https://fr.wikibooks.org/wiki/Wikijunior|consulté le=}}.</ref>. Ces deux évènements témoignent ainsi qu'il est toujours possible qu'un sous-projet apparaisse dans un projet Wikimédia. Comme autre exemple, il y a aussi le WikiJournal, un sous-projet développé au sein du projet Wikiversité en anglais et qui reçu le prix de l’''Open Publishing Awards'' en 2019<ref>{{Lien web|langue=|auteur=Open Publishing Awards|titre=Results|url=https://web.archive.org/web/20201125093419/https://openpublishingawards.org/|site=|consulté le=}}.</ref>. Une demande fut faite pour qu'il puisse bénéficier d'un nouveau site web dans le but de pouvoir se développer en dehors de Wikiversité. Malheureusement pour les initiateurs, la demande est restée sans suite jusqu'à ce jour<ref>{{Lien web|langue=|auteur1=Wikimedia Foundation Wiki|titre=Minutes/2020-02|url=https://web.archive.org/web/20201015115053/https://foundation.wikimedia.org/wiki/Minutes/2020-02#WikiJournal|site=|éditeur=|date=|consulté le=}}.</ref>, après que le conseil d’administration de la Fondation, chargé de répondre à celle-ci, considéra que le projet n’était pas suffisamment abouti. Il faut savoir qu'avant cela, le projet Wikiversité, dans lequel est né Wikijournal, avait lui-même été un sous-projet du projet Wikibook<ref>{{Lien web|auteur=Méta-Wiki|titre=Talk:Wikiversity/Old|url=https://web.archive.org/web/20130723232149/http://meta.wikimedia.org/wiki/Talk:Wikiversity/Old|date=|consulté le=}}.</ref>. Initialement, il visait à « créer une communauté de personnes qui se soutiennent mutuellement dans leurs efforts éducatifs<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''create a community of people who support each other in their educational endeavors »''</ref><ref>{{Lien web|auteur=Wikibooks|titre=Wikiversity|url=https://web.archive.org/web/20210506184146/https://en.wikibooks.org/wiki/Wikiversity|date=|consulté le=}}.</ref> ». Cependant, en août 2005, une longue discussion remit en question l’existence du sous-projet Wikiversité dans Wikibooks. Au terme de celle-ci, la décision fut prise de transférer Wikiversité et son contenu sur le site Méta-Wiki<ref name="Wikibooks">{{Lien web|titre=Wikibooks:Requests for deletion/Wikiversity|url=https://en.wikibooks.org/w/index.php?title=Wikibooks:Requests_for_deletion/Wikiversity&oldid=3490139|auteur=Wikibooks|consulté le=}}.</ref>, là où de nouvelles discussions ont abouti à l’idée de faire de Wikiversité un nouveau projet indépendant<ref>{{Lien web|titre=Wikiversity|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity&oldid=232819|auteur=Méta-Wiki|date=|consulté le=}}.</ref>. Déjà à l'époque, le conseil d’administration de la Fondation Wikimédia se montrait réticent à l'ouverture de nouveaux projets, et sa réaction fut de demander l'ouverture d'un sondage au sein de la communauté. Celui-ci devait rassembler une majorité des deux tiers en faveur de l'ouverture du nouveau site web<ref>{{Lien web|titre=Wikiversity/Vote/fr|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Vote/fr&oldid=316555|consulté le=|auteur=Méta-Wiki}}.</ref>. Un résultat qui fut finalement obtenu, mais pas sans de longs débats, dont voici un extrait<ref name="Wikibooks" />. <blockquote> La principale raison pour laquelle la Fondation Wikimédia ne veut pas "lâcher le morceau" est une simple question de bureaucratie et la crainte que le projet ne devienne une autre Wikispecies [un projet de répertoire du vivant]. Wikispecies est une idée cool, mais les "fondateurs" du projet se sont dégonflés à mi-chemin de la mise en place du contenu et ont décidé de faire une révision majeure qui a pris plus de temps que ce que tout le monde était prêt à mettre. Le même problème s’applique à Wikiversity en ce qui concerne la Fondation, parce que les objectifs et les buts de ce projet ne sont pas clairement définis, et il semble que les participants essaient de mordre plus qu’ils ne peuvent mâcher en proposant une université de recherche multi-collèges entière (avec un statut de recherche et une accréditation) à former de toutes pièces plutôt qu’un simple centre d’éducation pour adultes avec quelques classes.<ref>Texte original avant sa traduction par deepl.com/translator : « ''The main reason why the Wikimedia Foundation doesn't want to "turn it loose" is pure bureaucratic BS and a fear that it will turn into another Wikispecies. Wikispecies is a cool idea, but the "founders" of the project got cold feet part-way into putting in content and decided to do a major revision that took more time than anybody was willing to put into it. The same issue applies to Wikiversity so far as the Foundation is concerned, because the goals and purposes of this project are not clearly defined, and it seems like the participants are trying to bite off more than they can chew by proposing an entire multi-college research university (with Carnegie-Mellon research status and accreditation as well) to be formed out of whole cloth rather than a simple adult education center with a few classes. If more thought is done on how to "bootstrap" this whole project, perhaps some thoughts on how to convince the Foundation board to let a separate wiki be kicked loose to let this project try to develop on its own can be made''.--Rob Horning 11:21, 14 August 2005 (UTC) »</ref> </blockquote> En novembre 2005 et malgré les résultats positifs du sondage, l'indépendance du projet Wikiversité ne fut toutefois pas acceptée par cinq membres du conseil. Ceux-ci réclamaient une « réécriture de la proposition pour en exclure la remise de titre de compétence, la conduite de cours en ligne, et de clarifier le concept de plate-forme ''e-learning''<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''rewriting the proposal to'' ''exclude credentials, exclude online-courses and clarify the concept of elearning platform'' »</ref><ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Meetings/November 13, 2005|url=https://foundation.wikimedia.org/w/index.php?title=Meetings/November_13,_2005&oldid=118181|consulté le=}}.</ref> ». Quand ces rectifications furent faites, le projet bénéficia d'une période d’essai de plusieurs mois, jusqu'à ce que les amendements apportés au projet de départ soient enfin acceptés, le 31 juillet 2006. Ce long temps d'attente était justifié par la création du ''[[m:Special_projects_committee|special projects committee]]''<ref>{{Lien web|titre=Wikiversity/Modified project proposal|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Modified_project_proposal&oldid=395364#Scope_of_Wikiversity%20scope|auteur=Méta-Wiki|consulté le=}}.</ref>'','' qui, jusqu'en décembre 2021, fut chargé de soulager le conseil d’administration de la fondation, par rapport aux demandes de création de nouveaux projets Wikimédia<ref>{{Lien web|titre=Difference between revisions of "Special projects committee/Resolutions" - Meta|url=https://meta.wikimedia.org/w/index.php?title=Special_projects_committee/Resolutions&diff=prev&oldid=418944&diffmode=source|auteur=Méta-Wiki|consulté le=}}.</ref>. Un nouveau site, nommé Beta-Wikiversity, fut ainsi créé pour assister le lancement des différentes versions linguistiques de Wikiversité<ref>{{Lien web|titre=Wikiversité|url=https://fr.wikiversity.org/w/index.php?title=Wikiversité:Accueil&oldid=787344|auteur=Wikiversité|consulté le=}}.</ref>. Durant six mois, son premier objectif a d'abord été l'élaboration des lignes directrices concernant la potentialité de produire des recherches collaboratives au sein du projet<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''six months, during which guidelines for further potential uses of the site, including collaborative research, will be developed'' »</ref>. Par la suite, chaque nouvelle version linguistique, développée dans le projet Beta, devait avoir plus de 10 modifications par mois, réalisées par au moins trois personnes distinctes, avant de pouvoir bénéficier de son propre site web. À l'image de Beta-Wikiversity, le projet Wikisource<ref>{{Lien web|auteur=Wikisource|titre=Wikisource|url=https://web.archive.org/web/20210303213629/https://wikisource.org/wiki/Main_Page|date=|consulté le=}}.</ref> possède lui aussi un site indépendant pour lancer ces nouvelles déclinaisons linguistiques. Tandis que pour tous les autres projets pédagogiques, ce lancement s'effectue sur la plateforme [[incubator:Main_Page|Wikimedia Incubator]]<ref>{{Lien web|auteur=Wikimédia Incubator|titre=Welcome to Wikimedia Incubator!|url=https://web.archive.org/web/20210227091859/https://incubator.wikimedia.org/wiki/Incubator:Main_Page|date=|consulté le=}}.</ref>'','' créée à la même époque que Beta-Wikiversity. Ces trois plateformes de lancement ne concernent pas les nouveaux projets, qui doivent faire l'objet d'une acceptation par le conseil d'administration de la Fondation Wikimédia, et qui peuvent avoir pour origines des processus de création divers. Le projet Wikivoyage, par exemple, fut initialement créé en 2003 dans un Wiki extérieur au mouvement Wikimédia, là où il portait le nom de Wikitravel<ref>{{Lien web|auteur=Giles Turnbull|titre=The DIY travel guide|url=https://web.archive.org/web/20210116050802/http://news.bbc.co.uk/2/hi/uk_news/magazine/3614517.stm|site=BBC News|éditeur=|date=12 avril 2004|consulté le=}}.</ref>. Comme cela arrive parfois, ce projet sans but lucratif fut acheté par une entreprise commerciale en 2006. Mais en raison du changement de gouvernance et de l'apparition de publicités, une scission est apparue au sein de la communauté d’éditeurs. Les personnes désireuses de quitter Wikitravel lancèrent alors un nouveau site appelé Wikivoyage, qui reçut en 2007, le [[w:fr:Webby Award|''Webby Award'']] du meilleur guide de voyage Internet<ref>{{Lien web|auteur=Jake Coyle|titre=On the Net: Web Sites to Travel By|url=https://web.archive.org/web/20210121071600/https://www.foxnews.com/printer_friendly_wires/2007May30/0,4675,OntheNet,00.html|site=Fox News|date=30 mai 2007|consulté le=}}.</ref>. L'intégration de Wikivoyage dans l'écosystème Wikimédia n'a cependant été faite qu'en 2012, à la suite d'un appel à commentaires durant lequel 540 personnes sur 699 furent en faveur de l’intégration du projet<ref>{{Lien web|url=https://web.archive.org/web/20210311055050/https://meta.wikimedia.org/wiki/Requests_for_comment/Travel_Guide|titre=Requests for comment/Travel Guide|auteur=Méta-Wiki|consulté le=}}.</ref>. Comme cette nouvelle déclencha une migration importante depuis Wikitravel vers Wikivoyage, une plainte fut déposée par la société commerciale en charge du premier projet. Celle-ci fut toutefois rejetée et le projet Wikivoyage continua à prendre de l’ampleur au sein du mouvement Wikimédia, avec la création de nouvelles versions linguistiques<ref>{{Lien web|auteur=Steven Musil|titre=Wikimedia, Internet Brands settle Wikivoyage lawsuits|url=https://web.archive.org/web/20211116013544/https://www.cnet.com/tech/services-and-software/wikimedia-internet-brands-settle-wikivoyage-lawsuits/|site=CNET|éditeur=|date=17 février 2013|consulté le=}}.</ref>.[[Fichier:WikiMOOC - vidéo 23 - Les projets frères.webm|vignette|<small>Vidéo 1. Présentation des projets frères dans le cadre du WIKIMOOC 2016.</small>]]Le cas de Wikivoyage apparait toutefois comme une exception, car en général les nouveaux projets émergent des centaines de candidatures déposées sur le projet [[m:Proposals_for_new_projects|Méta-Wiki]]<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for new projects|url=https://web.archive.org/web/20211019173812/https://meta.wikimedia.org/wiki/Proposals_for_new_projects|date=|consulté le=}}.</ref>. Celles-ci se soldent bien souvent par un refus, comme ce fut le cas pour le projet WikiLang dont le but était de lancer un laboratoire linguistique<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=WikiLang|url=https://web.archive.org/web/20210109011449/https://meta.wikimedia.org/wiki/WikiLang|consulté le=}}</ref>. Quelques rares projets ont pourtant eu la chance d'être élus. Ce fut notamment le cas en octobre 2012, avec le lancement de la base de données structurée et sémantique [[w:Wikidata|Wikidata]] et de ses extensions [[w:Wikibase|Wikibase]], ou plus récemment, en 2020, avec l'arrivée du projet [[w:Abstract_Wikipedia|Abstract Wikipedia]]<ref>{{Lien web|langue=|auteur=Wikimedia Foundation Wiki|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20201026191716/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia|date=|consulté le=}}.</ref> et [[w:Wikifunctions|Wikifunctions]]<ref>{{Lien web|langue=|auteur=Wikimedia Fundation|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20200703234853/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia}}</ref>. Sans vouloir entrer dans les détails, il est intéressant de savoir que l'interconnexion entre ces trois projets permet de traduire automatiquement des articles encyclopédiques dans tous les langages naturels pris en charge par le mouvement Wikimédia. Plus précisément, les phrases des articles publiées sur Abstract Wikipédia, sont formulées par des fonctions informatiques produites dans le projet Wikifunctions, dans le but de traiter les informations de la base de données sémantique Wikidata. Autrement dit, un article dans Abstract Wikipédia ne possède qu'une seule page pour toutes les langues, pareillement aux pages d'entités de Wikidata, qui ont pour titre une lettre suivie d'un chiffre<ref>{{Lien web|langue=|auteur=Thomas Douillard|titre=Abstract Wikipédia - LinuxFr.org|url=https://web.archive.org/web/20200923155546/https://linuxfr.org/news/abstract-wikipedia#fn1|site=Linux Fr|lieu=|date=05/09/20|consulté le=}}.</ref>. À la suite de ces explications, on observe donc que ce n'est pas la complexité qui détermine le refus projet, mais plutôt une série de critères comparables à ceux retenus pour supprimer des projets ou versions linguistiques déjà existants. À ce propos, il existe sur Méta-Wiki une liste mise à jour des différents sites hébergés par la Fondation Wikimédia, dont l'existence est remise en cause<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for closing projects|url=https://web.archive.org/web/20210126030311/https://meta.wikimedia.org/wiki/Proposals_for_closing_projects|date=|consulté le=}}.</ref>. Dans celle-ci, on retrouve essentiellement des versions linguistiques de projets, qui n’ont pas réussi à poursuivre leurs développements, bien qu'un projet entier soit toujours susceptible d'être mis à l'arrêt. C'est d'ailleurs ce qui est arrivé aux 31 versions linguistiques du projet Wikinews, qui, en date du 4 mai 2026, soit 22 ans après le lancement du site anglophone, sont accessibles [[n:fr:Wikinews_ne_sera_plus_mis_à_jour_à_partir_du_4_mai_2026|en mode lecture uniquement]]<ref>{{Lien web|auteur=Wikinews|titre=Wikinews ne sera plus mis à jour à partir du 4 mai 2026|url=https://web.archive.org/web/20260413082226/https://fr.wikinews.org/wiki/Wikinews_ne_sera_plus_mis_%C3%A0_jour_%C3%A0_partir_du_4_mai_2026}}.</ref>. Dans le cas de Wikinews, ce fut le manque d'activité qui apparut comme principale justification de la suspension du projet. Cependant, d'autres raisons pourraient être invoquées, comme le prouve cet épisode de 2005, où, peu de temps après son lancement, la version francophone du recueil de citations [[w:Wikiquote|Wikiquote]] a bien risqué de disparaitre. Le projet fut effectivement accusé d’avoir récupéré le contenu d’une base de données soumise à un droit d’auteur, incompatible avec la licence Creative Commons appliquée sur l’ensemble des projets pédagogiques Wikimédia. Lorsqu'une plainte fut adressée à l’association Wikimédia France, pour être ensuite relayée sur le site Méta-Wiki, il fut bien question de fermer le projet<ref>{{Lien web|auteur1=Méta-Wiki|titre=Wikiquote FR/Closure of French Wikiquote|url=https://web.archive.org/web/20210204052058/https://meta.wikimedia.org/wiki/Wikiquote_FR/Closure_of_French_Wikiquote|date=}}.</ref>. Après de longues discussions, celui-ci fut toutefois maintenu, mais avec pour conditions de repartir de zéro, et d’établir une charte pour garantir la traçabilité des citations reprises par le projet<ref>{{Lien web|auteur1=Jean-Baptiste Soufron|titre=Redémarrage du Wikiquote Francophone / French Wikiquote Relaunch|url=https://web.archive.org/web/20200506074856/https://lists.wikimedia.org/pipermail/foundation-l/2006-March/019857.html|site=Foundation-l|lieu=|date=30 mars 2006}}.</ref>. Voici donc de quoi se faire une idée sur la manière dont les projets pédagogiques, et leurs déclinaisons linguistiques, apparaissent et disparaissent au sein du mouvement. Les exemples repris ci-dessus suffisent effectivement pour comprendre les principes généraux, qui sous-tendent leurs créations. En dehors de Méta-Wiki, Wikidata, Wikifunctions'','' Abstract Wikipédia et Wikimedia Commons, qui ne sont pas des projets de contenu pédagogique à proprement parler, tous les autres projets semblent effectivement provenir d’un désir de spécialisation d’un projet préexistant. L'idée est généralement débattue dans un projet de même langue, avant de relayer la discussion vers le site Méta-Wiki. Si le projet y est jugé pertinent, il fait alors l'objet d'une candidature. Celle-ci doit en effet être acceptée par le [[m:Wikimedia_Foundation_Community_Affairs_Committee/Sister_Projects_Task_Force/fr|groupe de travail des projets frères]], repris sous le mandat du [[m:Wikimedia_Foundation_Community_Affairs_Committee/fr|comité des affaires communautaires de la Fondation Wikimédia]], avant la création du nouveau site. Beaucoup de temps peut s'écouler entre l'idée et le lancement d'un projet, comme en témoigne Wikisources et ses trois ans de maturation<ref>{{Lien web|titre=The History of Wikisource|auteur=wikibooks|url=https://web.archive.org/web/20250615222118/https://en.wikibooks.org/wiki/Wikimedia/The_History_of_Wikisource}}</ref>. Quant aux nouvelles versions linguistiques, elles doivent être aujourd'hui soumises à l'approbation du [[m:Language_committee/fr|comité des langues]], avant d'être testées sur les plateformes Incubator, Beta-Wikiversité ou Wikisource multilingue, dans le but de bénéficier d’un site web indépendant. Après ces explications concernant les projets frères et leurs variations linguistiques, il nous reste encore à parler des sites qui ont un lien avec le mot Wiki, soit par leur nom, soit par le logiciel utilisé. En 2024 effectivement, plus de 22 600 d'entre eux étaient répertoriés, dont plus de 95 % sans aucun lien avec le mouvement Wikimédia, en dehors du fait, peut-être, qu’ils utilisaient le logiciel MediaWiki développé par la Fondation Wikimédia<ref>{{Lien web|langue=|auteur=WikiIndex|titre=the index of all wiki|url=https://web.archive.org/web/20240906224607/https://wikiindex.org/Category:All|site=|date=|consulté le=}}.</ref>. [[w:fr:WikiLeaks|WikiLeaks]] par exemple, créé par [[w:fr:Julian Assange|Julian Assange]] dans le but de publier des documents classifiés provenant de sources anonymes, n’est ni un projet Wikimédia, ni un site collaboratif. Quant au recueil universel et multilingue de guides illustrés [[w:fr:WikiHow|WikiHow]], si celui-ci fonctionne pour sa part de manière collaborative et avec le logiciel MediaWiki<ref>{{Lien web|auteur=WikiHow|titre=wikiHow:Powered and Inspired by MediaWiki|url=https://web.archive.org/web/20211030092737/https://www.wikihow.com/wikiHow:Powered-and-Inspired-by-MediaWiki|date=|consulté le=}}.</ref>, il n'a pourtant aucun lien avec le mouvement Wikimédia. D'ailleurs, son ergonomie, radicalement différente de celle des projets Wikimédia, permet de le comprendre au premier coup d’œil. En revanche, Wikimini, l'encyclopédie libre pour les enfants, a une apparence tout à fait comparable à celle des projets Wikimédia, alors que le projet n’a jamais été accepté par la Fondation. Quant aux projets [[w:fr:WikiTribune|WikiTribune]] et [[w:fr:Wikia|Fandom]], l'ambiguïté qu'ils entretiennent avec le mouvement est d'autant plus grande qu'ils ont été créés par [[w:fr:Jimmy Wales|Jimmy Wales]], le fondateur de Wikipédia et de la [[w:fr:Wikimedia Foundation|Fondation Wikimédia]]<ref>{{Lien web|langue=|auteur=Terry Collins|titre=Wikipedia co-founder launches project to fight fake news|url=https://web.archive.org/web/20201014061304/https://www.cnet.com/news/wikipedia-jimmy-wales-wikitribune-fighting-fake-news/|site=CNET|date=24 avril 2017|consulté le=}}.</ref>. Cependant, comme ce sont des projets commerciaux, ils ne peuvent en aucun cas être soutenus par une fondation sans but lucratif. Au terme de cette présentation, il ne reste plus qu'à signaler que le mouvement Wikimédia ne fut conscientisé que tardivement par rapport à l'apparition des projets Wikimédia et de leurs différentes versions linguistiques. Pour qu'un sentiment de collectivité se manifeste entre tous ceux-ci, il fallut effectivement attendre qu'une coordination se développe sur la plateforme Méta-Wiki, mais également que de nombreuses rencontres et associations apparaissent en dehors de l'espace numérique. En ce sens, la naissance du mouvement ne fut pas un événement ponctuel, mais plutôt la réalisation d’un long processus de conscientisation. {| class="wikitable"style="margin: auto;" "text-align:center;" |+Codes QR |[[Fichier:QR code page meta news.png|centré|sans_cadre|100x100px|lien=https://meta.wikimedia.org/wiki/Wikimedia_News]] |[[Fichier:QR-Code ligne du temps projets Wikimédia.png|centré|sans_cadre|100x100px|lien=https://upload.wikimedia.org/wikipedia/commons/b/b0/WikipediaTimeline.png]] |[[Fichier:Code QR vidéo présentation projets frères WikiMooc 2016.svg|centré|sans_cadre|100x100px|lien=https://upload.wikimedia.org/wikipedia/commons/8/89/WikiMOOC_-_vid%C3%A9o_23_-_Les_projets_fr%C3%A8res.webm]] |- |{{Centrer|<small>QR 10. Wikimedia News</small>}} |{{Centrer|<small>QR 11. Ligne du temps</small>}} |{{Centrer|<small>QR 12. Vidéo projets frères</small>}} |} {{AutoCat}} 9727dz0l5fzaxp6ymfljxrmpl6wfqjw Le mouvement Wikimédia/Cosmographie du mouvement Wikimédia 0 79278 765938 765677 2026-05-04T13:08:54Z Lionel Scheepmans 20012 765938 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> [[Fichier:Strategy Graphic.pdf|vignette|400x400px|<small>Figure 25. Graphique en anglais illustrant la complexité de l’écosystème Wikimédia.</small>]] Voici venu le moment de faire une présentation « cosmographique » de Wikimédia, ou pour le dire autrement, de présenter une sorte d’organigramme du mouvement en m’inspirant du mot galaxie, utilisé lors du dixième anniversaire du projet Wikipédia<ref>{{Article|langue=|prénom1=Nathalie|nom1=Savary|titre=La galaxie Wikimédia|périodique=Le Débat|volume=170|numéro=3|date=2012|issn=0246-2346|pages=138}}.</ref>. Pour ma part, la découverte de cette galaxie a débuté en surfant d’un projet Wikimédia à l’autre, avec l’intention de découvrir tout ce qui se cache dans l’ombre de la planète Wikipédia. Après quoi, il me restait à découvrir toute la partie hors ligne du mouvement. Ce que je fis à partir du 10 octobre 2011, en rejoignant les membres fondateurs de l’association Wikimédia Belgique<ref>{{Lien web|auteur=Méta-Wiki|titre=Wikimedia Belgium/Members|url=https://web.archive.org/web/20210305220128/https://meta.wikimedia.org/w/index.php?title=Wikimedia_Belgium%2FMembers&oldid=2979592|consulté le=}}.</ref>. Lors de mes aventures, j’ai découvert que les activités hors ligne et en ligne au sein du mouvement évoluaient dans deux sphères et environnements différents, dans lesquels j'ai pu observer des organisations et des comportements parfois très divergents<ref>{{Lien web|auteur=Lionel Scheepmans|titre=Wikimania et les différences entre les cultures en ligne et hors ligne|url=https://web.archive.org/web/20210526053609/https://diff.wikimedia.org/fr/2015/05/12/wikimania-et-les-differences-entre-les-cultures-en-ligne-et-hors-ligne/|site=Diff|année=05/05/2015|consulté le=}}.</ref>. Certaines [[w:fr:Dissonance cognitive|dissonances cognitives]] me sont même apparues, mais sans que je puisse pour autant déduire qu'il existe de réelles incohérences au sein du mouvement. Ce qui est dû, je pense, à une forte adhésion générale au projet commun, que représente le libre partage de la somme des connaissances<ref>{{Lien web|langue=|auteur=Wikimédia|titre=Wikimedia|url=https://web.archive.org/web/20201102041842/https://www.wikimedia.org/|site=|date=|consulté le=}}.</ref>. Au fil du temps, j’en suis venu à percevoir dans Wikimédia, une sorte de représentation miniature de cette hypercomplexité<ref>{{Ouvrage|langue=|prénom1=Lars|nom1=Qvortrup|titre=The hypercomplex Society|éditeur=Peter Lang|date=2003|isbn=978-0-8204-5704-8|oclc=59322051}}.</ref> qui caractérise notre société globale et numérique, depuis le début du XXIᵉ siècle. L’étude de l'organisation du mouvement me permit d'ailleurs de mieux comprendre les enjeux de la mondialisation structurelle et de la globalisation économique<ref>{{Lien web|auteur=Cynthia Ghorra-Gobin|titre=Notion en débat : mondialisation et globalisation|url=https://web.archive.org/web/20210127234505/http://geoconfluences.ens-lyon.fr/informations-scientifiques/a-la-une/notion-a-la-une/mondialisation-globalisation|site=Géoconfluences|lieu=|date=20/12/2017|consulté le=}}.</ref>, apparues sous de nouvelles formes, suite au développement d’Internet. En offrant dans cette deuxième partie d’ouvrage, une vue d’ensemble de ce mouvement international, j’espère enfin aider ses membres à s’y retrouver plus facilement. Quant aux autres lecteurs, ils trouveront dans cette présentation, une belle occasion de découvrir à quoi peut ressembler, aujourd'hui, une organisation cosmopolite, internationale, interculturelle, et fortement connectée grâce à l'usage d'Internet. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ ![[Fichier:QR code graphique écosystème Wikimédia.png|sans_cadre|100x100px|centré|lien=https://upload.wikimedia.org/wikipedia/commons/5/55/Strategy_Graphic.pdf]] ![[Fichier:QR code graphique galaxy Wikimédia.svg|sans_cadre|100x100px|centré|lien=https://upload.wikimedia.org/wikipedia/commons/c/c1/Galaxie_wikimedia.jpg]] |- |QR 13. Écosystème Wikimédia |QR 14. Galaxie Wikimédia |} {{Autocat}} 1k8g7z7sgvyj83yvnijj6dhkruz5v4g 765939 765938 2026-05-04T13:11:31Z Lionel Scheepmans 20012 765939 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> [[Fichier:Strategy Graphic.pdf|vignette|400x400px|<small>Figure 25. Graphique en anglais illustrant la complexité de l’écosystème Wikimédia.</small>]] Voici venu le moment de faire une présentation « cosmographique » de Wikimédia, ou pour le dire autrement, de présenter une sorte d’organigramme du mouvement en m’inspirant du mot galaxie, utilisé lors du dixième anniversaire du projet Wikipédia<ref>{{Article|langue=|prénom1=Nathalie|nom1=Savary|titre=La galaxie Wikimédia|périodique=Le Débat|volume=170|numéro=3|date=2012|issn=0246-2346|pages=138}}.</ref>. Pour ma part, la découverte de cette galaxie a débuté en surfant d’un projet Wikimédia à l’autre, avec l’intention de découvrir tout ce qui se cache dans l’ombre de la planète Wikipédia. Après quoi, il me restait à découvrir toute la partie hors ligne du mouvement. Ce que je fis à partir du 10 octobre 2011, en rejoignant les membres fondateurs de l’association Wikimédia Belgique<ref>{{Lien web|auteur=Méta-Wiki|titre=Wikimedia Belgium/Members|url=https://web.archive.org/web/20210305220128/https://meta.wikimedia.org/w/index.php?title=Wikimedia_Belgium%2FMembers&oldid=2979592|consulté le=}}.</ref>. Lors de mes aventures, j’ai découvert que les activités hors ligne et en ligne au sein du mouvement évoluaient dans deux sphères et environnements différents, dans lesquels j'ai pu observer des organisations et des comportements parfois très divergents<ref>{{Lien web|auteur=Lionel Scheepmans|titre=Wikimania et les différences entre les cultures en ligne et hors ligne|url=https://web.archive.org/web/20210526053609/https://diff.wikimedia.org/fr/2015/05/12/wikimania-et-les-differences-entre-les-cultures-en-ligne-et-hors-ligne/|site=Diff|année=05/05/2015|consulté le=}}.</ref>. Certaines [[w:fr:Dissonance cognitive|dissonances cognitives]] me sont même apparues, mais sans que je puisse pour autant déduire qu'il existe de réelles incohérences au sein du mouvement. Ce qui est dû, je pense, à une forte adhésion générale au projet commun, que représente le libre partage de la somme des connaissances<ref>{{Lien web|langue=|auteur=Wikimédia|titre=Wikimedia|url=https://web.archive.org/web/20201102041842/https://www.wikimedia.org/|site=|date=|consulté le=}}.</ref>. Au fil du temps, j’en suis venu à percevoir dans Wikimédia, une sorte de représentation miniature de cette hypercomplexité<ref>{{Ouvrage|langue=|prénom1=Lars|nom1=Qvortrup|titre=The hypercomplex Society|éditeur=Peter Lang|date=2003|isbn=978-0-8204-5704-8|oclc=59322051}}.</ref> qui caractérise notre société globale et numérique, depuis le début du XXIᵉ siècle. L’étude de l'organisation du mouvement me permit d'ailleurs de mieux comprendre les enjeux de la mondialisation structurelle et de la globalisation économique<ref>{{Lien web|auteur=Cynthia Ghorra-Gobin|titre=Notion en débat : mondialisation et globalisation|url=https://web.archive.org/web/20210127234505/http://geoconfluences.ens-lyon.fr/informations-scientifiques/a-la-une/notion-a-la-une/mondialisation-globalisation|site=Géoconfluences|lieu=|date=20/12/2017|consulté le=}}.</ref>, apparues sous de nouvelles formes, suite au développement d’Internet. En offrant dans cette deuxième partie d’ouvrage, une vue d’ensemble de ce mouvement international, j’espère aider ses membres à s’y retrouver plus facilement. Quant aux autres lecteurs, ils trouveront dans cette présentation, une belle occasion de découvrir à quoi peut ressembler, aujourd'hui, une organisation cosmopolite, internationale, interculturelle, et fortement connectée grâce à l'usage d'Internet. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ ![[Fichier:QR code graphique écosystème Wikimédia.png|sans_cadre|100x100px|centré|lien=https://upload.wikimedia.org/wikipedia/commons/5/55/Strategy_Graphic.pdf]] ![[Fichier:QR code graphique galaxy Wikimédia.svg|sans_cadre|100x100px|centré|lien=https://upload.wikimedia.org/wikipedia/commons/c/c1/Galaxie_wikimedia.jpg]] |- |QR 13. Écosystème Wikimédia |QR 14. Galaxie Wikimédia |} {{Autocat}} jh0p6zeznsncqq6aik4tb9mh6bgxfv7 Le mouvement Wikimédia/La constellation des projets en ligne 0 79286 765954 765865 2026-05-04T14:35:37Z Lionel Scheepmans 20012 765954 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Comme dit précédemment, l’espace numérique Wikimédia le plus connu du grand public est Wikipédia, avec en mai 2026, 67 millions d’articles en provenance de 245 versions linguistiques actives<ref>{{Lien web|langue=|titre=List of Wikipedias/fr|url=https://web.archive.org/web/20260503080719/https://meta.wikimedia.org/w/index.php?uselang=fr&title=List_of_Wikipedias%2Ffr|site=|date=|consulté le=|auteur=Méta-Wiki}}.</ref>. Elle arrive ainsi en tête du classement des projets pédagogiques, avant les 198 versions du Wiktionnaire, 122 de Wikibooks, 99 de Wikiquote, 84 de Wikisource, 36 de Wikinews (lecture seulement), 27 de Wikivoyage et 17 de Wikiversity, pour un total de 884 projets pédagogiques<ref>{{Lien web|langue=|auteur=Wikipédia|titre=Liste des Wikis de Wikimedia|url=https://web.archive.org/web/20260410065811/https://fr.wikipedia.org/wiki/Sp%C3%A9cial:Matrice_des_sites|site=|date=|consulté le=}}.</ref>. À cela s'ajoutent encore et toujours au niveau pédagogique, le projet multilingue Wikispecies et les trois plateformes de lancement des nouvelles versions linguistiques que sont Incubator, BetaWikiversity et Wikisource multilingue. En ajoutant les projets technique, administratif, et autres, on arrive ainsi, en février 2026 et selon Wikiscan, à un total de 1009 sites web et 602 millions de pages hébergées sur les serveurs de la Fondation<ref>{{Lien web|langue=|auteur=Wikiscan|titre=Statistics|url=https://web.archive.org/web/20250813173246/https://wikiscan.org/|site=|date=|consulté le=}}.</ref>. Même s’ils sont développés au sein d’un même mouvement et avec le même logiciel informatique (MediaWiki), tous ces sites ne se ressemblent pas pour autant. En décembre 2025 par exemple, et si l’on se fie cette fois à WikiStats, on découvre que le nombre de personnes qui ont réalisé au moins une modification, varie de 189, pour Wikiquote en [[w:Ourdou|ourdou]], à 52 624 972, pour [[w:Wikipédia en anglais|Wikipédia en anglais]]<ref name="Wikistat1">{{Lien web|langue=|auteur=WikiStats|titre=All Wikimedia Projects by Size|url=https://web.archive.org/web/20260504135644/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_desc&th=0&lines=1010|consulté le=}}.</ref>. Cela alors que chaque site fonctionne avec un [[w:système de gestion de contenu|système de gestion de contenu]] configuré différemment<ref>{{Lien web|auteur=Github|titre=wikimedia / operations-mediawiki-config|url=https://web.archive.org/web/20210216065944/https://github.com/wikimedia/operations-mediawiki-config/blob/master/wmf-config/InitialiseSettings.php#L18792-L18797|consulté le=}}.</ref>, et des règles spécifiques adaptées à des objectifs pédagogiques distincts, et aux différences linguistiques et donc culturelles. Le projet [[w:Wikipédia en allemand|Wikipédia germanophone]] par exemple, possède des règles distinctes à celles du projet francophone. Le ''[[w:fair use|fair use]]'' n’y est pas d’application, les articles à l’état d’ébauche ne sont pas conservés et le bannissement d’un utilisateur ou d’une utilisatrice nécessite un vote favorable atteignant la majorité des deux tiers<ref>{{Lien web|langue=|titre=Wikipédia en allemand|url=https://web.archive.org/web/20201117104946/https://fr.wikipedia.org/wiki/Wikip%C3%A9dia_en_allemand#Caract%25C3%25A9ristiques|site=|date=|consulté le=|auteur=Wikipédia}}.</ref>. Cela alors qu’en octobre 2020 et uniquement dans le projet en portugais, il fut décidé que la modification de l’espace principal de l’encyclopédie serait dorénavant réservée aux personnes bénéficiant d’un compte utilisateur. Quant au contenu des différentes versions linguistiques de l’encyclopédie libre, une étude de 2010, qui comparait 74 d’entre elles, a mis en évidence que 74 % des articles encyclopédiques n’existaient que dans une seule version linguistique<ref>{{Article|langue=|auteur=|nom1=Hecht B|nom2=Gergle D|prénom3=CHI 2010|nom3=28th Annual CHI Conference on Human Factors in Computing Systems|titre=The tower of Babel meets web 2.0:User-generated content and its applications in a multilingual context|périodique=Conf Hum Fact Comput Syst Proc Conference on Human Factors in Computing Systems – Proceedings|volume=1|date=2010|issn=|pages=291–300}}.</ref>. Ce qui prouve donc que chaque version linguistique possède bien son propre contenu, alors que celui-ci est développé par des éditrices et éditeurs qui communiquent dans une langue commune, mais sans pour autant partager la même culture ou la même nationalité. De plus, les projets pédagogiques ont chacun leur propre finalité. L’objectif d’une encyclopédie, étant effectivement différent de celui d’un dictionnaire, d’un guide de voyage, d’un répertoire du vivant, d’un recueil de citations, d’une plateforme de création de cours et travaux de recherches, d’une bibliothèque de livres repris dans le domaine public ou publiés sous licence libre, d’un site journalistique, d’une banque de données sémantiques, d’une autre de fonctions informatiques ou de fichiers médiatiques, etc. Ensuite, qui dit finalités différentes, dit aussi règles éditoriales différentes. Il est effectivement interdit de produire du nouveau savoir, ou d’abuser de sources primaires sur Wikipédia, alors que parallèlement, tout cela est le bienvenu sur l’ensemble des autres projets, à l’image du Wiktionnaire<ref>{{Lien web|auteur1=Noé Gasparini|titre=Nature des sources et neutralité - Wiktionnaire|url=https://upload.wikimedia.org/wikipedia/commons/9/9d/Nature_des_sources_et_neutralit%C3%A9_dans_le_Wiktionnaire.pdf?uselang=fr|site=Wikimedia Commons|date=20 octobre 2017|page=34}}.</ref>. Wikisource faisant toutefois exception à cette dernière règle, puisque le projet consiste à collecter et à numériser des ouvrages répertoriés dans le domaine public ou déjà publiés sous licence libre. Enfin, il faut se souvenir que le droit d’auteur peut aussi varier d’un projet à l’autre. Vu que les données reprises sur Wikidata par exemple, ainsi que les descriptions de fichiers reprises sur Wikimedia Commons, sont soumises à la licence CC0, contrairement au contenu des autres projets qui est sous licence CC.BY-SA. Un dernier argument qui justifie donc la nécessité d’effectuer un classement des projets et autres espaces d’activités numériques par fonctions<ref>{{Lien web|langue=|titre=Wikimedia projects/fr|url=https://web.archive.org/web/20201010052610/https://meta.wikimedia.org/w/index.php?title=Wikimedia_projects/fr&uselang=fr|site=|date=|consulté le=|auteur=Méta-Wiki}}.</ref>, de sorte à rendre plus compréhensible leur vue d’ensemble. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ ![[Fichier:QR_code_Wikiscan.png|sans_cadre|100x100px|centré|lien=https://wikiscan.org]] ![[Fichier:QR code Wikistats.png|sans_cadre|100x100px|centré|lien=https://stats.wikimedia.org/#/all-projects]] |- |QR 13. Wikiscan |QR 14. Wikistat |} {{AutoCat}} 14ol0kfvnm3nyn3f6bwpc97zejeqe22 Discussion Wikilivres:Le Bistro/2026 5 83406 765988 765256 2026-05-04T20:43:24Z MediaWiki message delivery 36013 /* Actualités techniques n° 2026-19 */ nouvelle section 765988 wikitext text/x-wiki == Actualités techniques n° 2026-03 == <section begin="technews-2026-W03"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/03|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * La Fondation Wikimedia a publié des questions directrices pour son plan annuel de juillet 2026 à juin 2027 sur les plateformes [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] et ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. Celles-ci portent sur les tendances mondiales, une expérimentation plus rapide et plus constructive, un meilleur accompagnement des nouveaux contributeurs, le renforcement du rôle des éditeurs et des utilisateurs avancés, l'amélioration de la collaboration entre les projets, ainsi que le développement et la fidélisation du lectorat. Des commentaires et suggestions sont les bienvenus sur la [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|page de discussion]]. '''Actualités pour la contribution''' * Dans le cadre des travaux en cours de l'équipe technique communautaire sur le projet [[m:Special:MyLanguage/Community Wishlist/W372|Listes de surveillance multiples]], l'affichage de [[Special:EditWatchlist|Modifier la liste de surveillance]] sera mis à jour entant que qu'une première étape vers la prise en charge de plusieurs listes de surveillance. De plus, la pagination de [[Special:Search|Recherche]] sera également mise à jour, dans le cadre du travail sur le souhait [[m:Special:MyLanguage/Community Wishlist/W186|Refonte de la pagination / navigation des pages]]. [https://phabricator.wikimedia.org/T411596] * [[m:Special:GlobalWatchlist|La Liste de Surveillance Globale]] est une [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] de MediaWiki qui vous permet de voir vos listes de surveillance provenant de différents wikis sur la même page. Il a récemment été mis à jour pour ressembler davantage à la [[Special:Watchlist|Liste de surveillance]] régulière, par exemple en le préparant pour les comptes temporaires dans le masquage IP (y compris le réacheminement des liens des utilisateurs vers les pages de contributions), en mettant les titres de page en gras et en ouvrant les liens dans les résumés d'édition et les balises dans de nouveaux onglets du navigateur. [https://phabricator.wikimedia.org/T398361][https://phabricator.wikimedia.org/T298919][https://phabricator.wikimedia.org/T273526][https://phabricator.wikimedia.org/T286309] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:28|la tâche soumise|les {{formatnum:28}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:28||s}} la semaine dernière]]. Par exemple, le problème selon lequel les blocs globaux ne disposaient pas de l'option permettant de désactiver l'envoi d'e-mails a maintenant été résolu et sera disponible à l'utilisation à partir de la semaine du 13 janvier. [https://phabricator.wikimedia.org/T401293] '''Actualités pour la contribution technique''' * L'[[mw:Special:MyLanguage/VisualEditor/Citation tool|outil de citation VisualEditor]] et les [[mw:Special:MyLanguage/Help:Reference Previews|Aperçus de référence]] prennent désormais en charge "carte" comme type de référence. [https://phabricator.wikimedia.org/T411083] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.10|MediaWiki]]/[[mw:MediaWiki 1.46/wmf.11|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/03|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W03"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 12 janvier 2026 à 20:33 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29907192 --> == Thank You for Last Year – Join Wiki Loves Ramadan 2026 == Dear Wikimedia communities, We hope you are doing well, and we wish you a happy New Year. ''Last year, we captured light. This year, we’ll capture legacy.'' In 2025, communities around the world shared the glow of Ramadan nights and the warmth of collective iftars. In 2026, ''Wiki Loves Ramadan'' is expanding, bringing more stories, more cultures, and deeper global connections across Wikimedia projects. We invite you to explore the ''Wiki Loves Ramadan 2026'' [[m:Special:MyLanguage/Wiki Loves Ramadan 2026|Meta page]] to learn how you can participate and [[m:Special:MyLanguage/Wiki Loves Ramadan 2026/Participating communities|sign up]] your community. 📷 ''Photo campaign on '' [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan 2026|Wikimedia Commons]] If you have questions about the project, please refer to the FAQs: * [[m:Special:MyLanguage/Wiki Loves Ramadan/FAQ/|Meta-Wiki]] * [[c:Special:MyLanguage/Commons:Wiki Loves Ramadan/FAQ|Wikimedia Commons]] ''Early registration for updates is now open via the '''[[m:Special:RegisterForEvent/2710|Event page]]''''' ''Stay connected and receive updates:'' * [https://t.me/WikiLovesRamadan Telegram channel] * [https://lists.wikimedia.org/postorius/lists/wikilovesramadan.lists.wikimedia.org/ Mailing list] We look forward to collaborating with you and your community. '''The Wiki Loves Ramadan 2026 Organizing Team''' 16 janvier 2026 à 20:44 (CET) <!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29879549 --> == <span lang="en" dir="ltr">Tech News: 2026-04</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W04"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/04|Translations]] are available. '''Updates for editors''' * The tray shown on [[Special:Diff|Special:Diff]] in mobile view has been redesigned. It is now collapsed by default, and incorporates a link to undo the edit being viewed, making it easier for mobile editors and reviewers to take action while keeping the interface uncluttered. [https://phabricator.wikimedia.org/T402297] * [[m:Special:GlobalWatchlist|The Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continues to improve — it now automatically determines the text direction (ensuring correct display of sites with unusual domain names) and shows detailed descriptions for log actions. Later this week, a new permanent link for page creations and CSS classes for each entry element will be added. [https://phabricator.wikimedia.org/T412505][https://phabricator.wikimedia.org/T287929][https://phabricator.wikimedia.org/T262768][https://phabricator.wikimedia.org/T414135] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:32}} community-submitted {{PLURAL:32|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the previously observed issue in Vector 2022, where anchor link targets were obscured by the sticky header, has now been addressed. [https://phabricator.wikimedia.org/T406114] '''Updates for technical contributors''' * As mentioned in the [[m:Special:MyLanguage/Tech/News/2025/44|October 2025 deprecation announcement]], MediaWiki Interfaces team will begin sunsetting all transform endpoints containing a trailing slash from the MediaWiki REST API the week of January 26. Changes are expected to roll out to all wikis on or before January 30th. All API users currently calling them are encouraged to transition to the non-trailing slash versions. Both endpoint variations can be found, compared, and tested using the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox]. If you have questions or encounter any problems, please file a ticket in Phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board]. * Interactive reference documentation for the [[mw:Special:MyLanguage/Wikimedia REST API|Wikimedia REST API]] has moved. Requests to API docs previously hosted through [[mw:Special:MyLanguage/RESTBase|RESTBase]] (e.g.: <code dir=ltr>https://en.wikipedia.org/api/rest_v1/</code>) are now redirected to the [[w:en:Special:RestSandbox|REST Sandbox]]. * The [[mw:Special:MyLanguage/Wikidata Platform|WMF Wikidata Platform team]] (WDP) has published its [[d:Special:MyLanguage/Wikidata:Wikidata Platform team/Newsletter|January 2026 newsletter]]. It includes updates on the legacy full-graph endpoint decommissioning, the User-Agent policy change, the monthly Blazegraph migration office hours, and efforts to reduce regressions caused by the legacy endpoint shutdown. As a reminder, you can [[m:Special:MyLanguage/Global message delivery/Targets/WDP team updates|subscribe to the WDP newsletter]]! * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.12|MediaWiki]] '''Meetings and events''' * The [[mw:Wikimedia Hackathon Northwestern Europe 2026|Wikimedia Hackathon Northwestern Europe 2026]] will take place on 13-14 March 2026 in Arnhem, the Netherlands. Applications opened mid-December and will close soon or when capacity is reached. It's a two-day, technically oriented hackathon bringing together Wikimedians from the region. Hope to see you there! '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/04|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W04"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 19 janvier 2026 à 21:29 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29943403 --> == Révision annuelle du code universel de conduite et des lignes directrices de l'application == <section begin="announcement-content" /> Nous vous informons que la période de relecture annuelle du Code de conduite universel et des règles d'applications est actuellement ouverte. Vous pouvez faire vos commentaires sur les modifications que vous souhaitez apporter jusqu'au 9 février 2026. C'est la première d'une série d'étapes nécessaires pour la révision annuelle. Vous trouverez [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2026|d'autres informations et les discussions auxquelles participer sur la page UCoC de Meta]]. Le [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Comité de coordination du code universel de conduite]] (U4C &mdash; Universal Code of Conduct Coordinating Committee) est un groupe global dont le rôle est de fournir une implémentation équitable et cohérente de l'UCoC. Cette relecture annuelle a été envisagée et mise en place par l'U4C. Pour plus d'informations et les responsabilités de l'U4C, veuillez lire la [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|Charte de l'U4C]]. Veuillez partager ces informations avec les autres membres concernés de votre communauté. -- En coopération avec l'U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|discussion]])<section end="announcement-content" /> 19 janvier 2026 à 22:01 (CET) <!-- Message envoyé par User:Keegan (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=29905753 --> == Actualités techniques n° 2026-05 == <section begin="technews-2026-W05"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/05|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * La Fondation Wikimedia invite à donner des commentaires sur [[m:Special:MyLanguage/Product and Technology Advisory Council/Year1 Reflections and Proposed Way Forward 2026 Update|l’avenir proposé]] du [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif des produits et technologies]] jusqu’au 28 février. * Tous les utilisateurs disposant d'un compte enregistré peuvent désormais utiliser des clés d'accès pour la [[m:Special:MyLanguage/Help:Two-factor authentication|double authentification]] (2FA). Les clés d'accès sont un moyen simple de se connecter sans utiliser un second appareil. Elles vérifient l'identité de l'utilisateur à l'aide d'une empreinte digitale, d'une reconnaissance faciale ou d'un code PIN. Pour configurer une clé d'accès, configurez d'abord une méthode 2FA classique. Actuellement, pour se connecter avec une clé d'accès, les utilisateurs doivent également utiliser un mot de passe. Plus tard ce trimestre, la connexion sans mot de passe permettra aux utilisateurs de se connecter d'un simple clic avec une clé d'accès. Les utilisateurs disposant de droits avancés devront également avoir la 2FA activée. Cela fait partie du projet [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security|Sécurité du compte]]. * Les contributeurs non enregistrés sur des IP bloquées ou des plages d'IP bloquées peuvent désormais interagir sur le wiki pour faire appel d'un blocage en créant un compte temporaire afin de contester un blocage sur la page de discussion de l'utilisateur, sauf si l'option « empêcher cet utilisateur de modifier sa propre page de discussion » est activée. Cela résout le problème des utilisateurs déconnectés incapables d'utiliser le processus de déblocage par défaut via la page de discussion de l'utilisateur. [https://phabricator.wikimedia.org/T398673] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:20|la tâche soumise|les {{formatnum:20}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:20||s}} la semaine dernière]]. Par exemple, la description des méthodes d'authentification à deux facteurs (2FA) sur la page de gestion a été mise à jour. Il est désormais plus clair et plus facile pour les utilisateurs à comprendre et à utiliser. [https://phabricator.wikimedia.org/T332385] '''Actualités pour la contribution technique''' * Une nouvelle variable AbuseFilter, <code>account_type</code>, a été ajoutée pour fournir un moyen fiable de déterminer le type de compte créé dans les actions <code>createaccount</code> et <code>autocreateaccount</code>. Dans le cadre de ce changement, la variable <code>accountname</code> a été renommée en <code>account_name</code>, et <code>accountname</code> est désormais obsolète. Les gestionnaires de filtres doivent mettre à jour tous les filtres qui utilisent des vérifications de type de compte codées en dur ou la variable obsolète. [https://phabricator.wikimedia.org/T414049] * Les vignettes d'images demandées dans des tailles non standard, et en utilisant des méthodes non standard telles que les requêtes directes à <code dir=ltr><nowiki>upload.wikimedia.org/…</nowiki></code>, cesseront de fonctionner dans un proche avenir. Ce changement vise à prévenir les abus externes continus par des robots et des aspirateurs web. Certains utilisateurs ayant des CSS/JS personnalisés, les administrateurs d'interface qui peuvent corriger les gadgets et les thèmes locaux, ainsi que les auteurs d'outils, devront mettre à jour leur code pour utiliser des tailles de vignettes standard. [[phab:T414805|Des détails, des liens de recherche et des exemples de correction sont disponibles dans la tâche]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.13|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/05|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W05"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 26 janvier 2026 à 22:17 (CET) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=29969530 --> == <span lang="en" dir="ltr">Tech News: 2026-06</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W06"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/06|Translations]] are available. '''Updates for editors''' * The "{{int:pageinfo-toolboxlink}}" feature, which gives validating information about a page ([{{fullurl:{{FULLPAGENAME}}|action=info}} example]), now automatically includes a table of contents. If there is a local [[{{ns:8}}:Pageinfo-header]] page created by individual users, it can now be removed. [https://phabricator.wikimedia.org/T363726] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, VisualEditor previously added bold or italic formatting inside link descriptions, making the wikicode complex. This has now been fixed. [https://phabricator.wikimedia.org/T409669] '''Updates for technical contributors''' * There was no XML dump on 20 January. Additionally, from now on, dumps will be generated once per month only. [https://phabricator.wikimedia.org/T414389] * The MediaWiki Interfaces team removed support for all transform endpoints containing a trailing slash from the [https://www.mediawiki.org/wiki/Special:MyLanguage/API:REST%20API MediaWiki REST API]. All API users currently calling those endpoints are encouraged to transition to the non-trailing slash versions. If you have questions or encounter any problems, please file a ticket in phabricator to the [https://phabricator.wikimedia.org/project/view/6931/ #MW-Interfaces-Team board]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.14|MediaWiki]] '''Weekly highlight''' * Users are reminded that the Wikimedia Foundation has shared some guiding questions for the July 2026–June 2027 Annual Plan on [[m:Special:MyLanguage/Wikimedia Foundation Annual Plan/2026-2027/Product & Technology OKRs|Meta]] and ''[[diffblog:2025/12/10/shaping-wikimedia-foundations-2026-2027-annual-goals-key-questions-for-the-wikimedia-movement/|Diff]]''. These focus on global trends, faster and healthier experimentation, better support for newcomers, strengthening editors and advanced users, improving collaboration across projects, and growing and retaining readership. Feedback and ideas are welcome on the [[m:Talk:Wikimedia Foundation Annual Plan/2026-2027|talk page]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/06|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W06"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 février 2026 à 18:43 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30000986 --> == Actualités techniques n° 2026-07 == <section begin="technews-2026-W07"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/07|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] Les contributeurs connectés qui gèrent de grandes ou complexes listes de suivi peuvent désormais organiser et filtrer les pages surveillées de manière à améliorer leurs flux de travail grâce à la nouvelle fonctionnalité [[mw:Special:MyLanguage/Help:Watchlist labels|Étiquettes de liste de suivi]]. En ajoutant des étiquettes personnalisées (par exemple : pages que vous avez créées, pages surveillées pour vandalisme, ou pages de discussion), les utilisateurs peuvent identifier plus rapidement ce qui nécessite une attention, réduire la charge cognitive et répondre plus efficacement. Cela améliore l'utilisabilité de la liste de suivi, en particulier pour les éditeurs très actifs. * Une nouvelle fonctionnalité disponible sur [[Special:Contributions|Special:Contributions]] montre [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts|des comptes temporaires]] qui sont probablement utilisés par la même personne, et rend ainsi le patrouillage moins chronophage. En vérifiant les contributions d'un compte temporaire, les utilisateurs ayant accès aux adresses IP des comptes temporaires peuvent désormais avoir une vue des contributions des comptes temporaires associés. La fonctionnalité recherche toutes les adresses IP associées à un compte temporaire donné pendant la période de conservation des données et affiche toutes les contributions de tous les comptes temporaires ayant utilisé ces adresses IP. [[mw:Special:MyLanguage/Trust and Safety Product/Temporary Accounts#February 2026: Improvements to the patroller tooling|Plus...]] [https://phabricator.wikimedia.org/T415674] * Lorsque les éditeurs prévisualisent une modification de wikitexte, la boîte de rappel indiquant qu'ils ne voient qu'une prévisualisation (qui est affichée en haut) a désormais un fond gris/neutre au lieu d'un fond jaune/d'avertissement. Cela facilite la distinction entre les notes de prévisualisation et les avertissements réels (par exemple, les conflits de modification ou les cibles de redirection problématiques), qui seront désormais affichés dans des boîtes d'avertissement ou d'erreur séparées. [https://phabricator.wikimedia.org/T414742] * La [[m:Special:GlobalWatchlist|Liste de suivi globale]] vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page. L' [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] continue de s'améliorer — elle prend désormais en charge correctement plus d'un site Wikibase, par exemple à la fois [[d:|Wikidata]] et [[testwikidata:|testwikidata]]. De plus, des problèmes concernant la direction du texte ont été résolus pour les utilisateurs qui préfèrent Wikidata ou d'autres sites Wikibase dans des langues de droite à gauche (RTL). [https://phabricator.wikimedia.org/T415440][https://phabricator.wikimedia.org/T415458] * <span lang="en" dir="ltr" class="mw-content-ltr">The automatic "magic links" for ISBN, RFC, and PMID numbers have been [[mw:Special:MyLanguage/Help:Magic links|deprecated in wikitext since 2021]] due to inflexibility and difficulties with localization. Several wikis have successfully replaced RFC and PMID magic links with equivalent external links, but a template was often required to replace the functionality of the ISBN magic link. There is now a new [[mw:Special:MyLanguage/Help:Magic words#isbn|built-in parser function]] <code dir=ltr><nowiki>{{#isbn}}</nowiki></code> available to replace the basic functionality of the ISBN magic link. This makes it easier for wikis who wish to migrate off of the deprecated magic link functionality to do so.</span> [https://phabricator.wikimedia.org/T145604] * Deux nouveaux wikis ont été créés : ** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q35401|Jju]] ([[w:kaj:|<code>w:kaj:</code>]]) [https://phabricator.wikimedia.org/T413283] ** un {{int:project-localized-name-group-wikipedia}} dans [[d:Q1186896|Nawat]] ([[w:ppl:|<code>w:ppl:</code>]]) [https://phabricator.wikimedia.org/T413273] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. '''Actualités pour la contribution technique''' * Un nouveau groupe d'utilisateurs global a été créé : [[{{int:grouppage-local-bot}}|{{int:group-local-bot}}]]. Il sera utilisé en interne par le logiciel pour permettre aux robots communautaires de contourner les limites de débit appliquées aux [[w:en:Web_scraping|web scrapers]] abusifs. Les comptes approuvés en tant que robots sur au moins un wiki Wikimedia seront automatiquement ajoutés à ce groupe. Cela ne changera pas les autorisations dont dispose le robot. [https://phabricator.wikimedia.org/T415588] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.15|MediaWiki]] '''Rencontres et évènements''' * La [[mw:Special:MyLanguage/MediaWiki Users and Developers Conference Spring 2026|Conférence des utilisateurs et des développeurs de MediaWiki, Printemps 2026]] se tiendra du 25 au 27 mars à Salt Lake City, États-Unis. Cet événement est organisé par et pour la communauté MediaWiki de tiers. Vous pouvez proposer des sessions et vous inscrire pour y assister. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/AZBWVI46SDEB65PGR5J6E4TYOQQEZXM7/] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/07|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W07"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10 février 2026 à 00:30 (CET) <!-- Message envoyé par User:Quiddity (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30026671 --> == Actualités techniques n° 2026-08 == <section begin="technews-2026-W08"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/08|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * <span class="mw-translate-fuzzy">L'[[mw:Special:MyLanguage/Wikimedia Site Reliability Engineering|équipe SRE]] va procéder au nettoyage d'[[m:Special:MyLanguage/Etherpad|Etherpad]], l'éditeur web open source de documents collaboratifs en temps réel. Tous les blocs-notes seront définitivement supprimés après le 30 avril 2026 – si des projets de migration sont encore en cours à cette date, l'équipe pourra réexaminer la date au cas par cas. Veuillez effectuer des sauvegardes locales de tout contenu que vous souhaitez conserver, car les données supprimées ne pourront pas être récupérées. Ce nettoyage permet de réduire la taille de la base de données et l'empreinte de l'infrastructure. Etherpad continuera de prendre en charge la collaboration en temps réel, mais le stockage à long terme n'est plus assuré. D'autres nettoyages pourront avoir lieu ultérieurement sans préavis.</span> [https://phabricator.wikimedia.org/T415237] '''Actualités pour la contribution''' * L'équipe de Recherche d'Informations lancera une [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|expérimentation sur l'application mobile Android]], afin de tester des fonctionnalités de recherche hybrides capables de gérer à la fois les requêtes sémantiques et par mots-clés. L'amélioration de la recherche sur la plateforme permettra aux lecteurs de trouver plus facilement ce qu'ils cherchent, directement sur Wikipédia. L'expérimentation sera d'abord lancée sur Wikipédia en grec fin février, puis sur les versions anglaise, française et portugaise en mars. [https://diff.wikimedia.org/2026/01/08/semantic-search-making-it-easier-to-find-the-information-readers-want/ En savoir plus] sur le blog ''Diff''. [https://www.mediawiki.org/wiki/Readers/Information_Retrieval] * L'équipe « Croissance des lecteurs » mènera [[mw:Special:MyLanguage/Readers/Reader Growth/WE3.10.2 Mobile Table of Contents|une expérience]] auprès des utilisateurs de la version mobile du site web qui ajoute une table des matières et développe automatiquement toutes les sections des articles, afin de mieux comprendre les problèmes de navigation qu'ils rencontrent. Le test sera disponible sur les versions arabe, chinoise, anglaise, française, indonésienne et vietnamienne de Wikipedia. * Auparavant, les notifications ([[{{ns:8}}:Sitenotice]] et [[{{ns:8}}:Anonnotice]]) du site ne s'affichaient que sur la version ordinateur. Maintenant, elles s'afficheront désormais sur toutes les plateformes. Les utilisateurs mobiles verront ces notifications. Les administrateurs du site doivent être prêts à tester et à corriger les notifications sur les appareils mobiles afin d'éviter toute interférence avec les articles. Pour désactiver ces notifications, les administrateurs d'interface peuvent ajouter <code dir="ltr">#siteNotice { display: none; }</code> à [[{{ns:8}}:Minerva.css]]. [https://phabricator.wikimedia.org/T138572][https://phabricator.wikimedia.org/T416644] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:19|la tâche soumise|les {{formatnum:19}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:19||s}} la semaine dernière]]. Par exemple, un problème concernant la section ''[[Special:RecentChanges|Spécial:Modifications récentes]]'' a été résolu. Auparavant, cliquer sur « Masquer » dans les filtres actifs entraînait la disparition du bouton « Afficher les nouvelles modifications depuis… », alors qu'il aurait dû rester visible. Ce bouton fonctionne désormais correctement. [https://phabricator.wikimedia.org/T406339] '''Actualités pour la contribution technique''' * Une nouvelle documentation est désormais disponible pour aider les rédacteurs à déboguer les fonctionnalités de recherche interne. Elle facilite le dépannage lorsque des pages n'apparaissent pas dans les résultats, lorsque le classement semble inattendu et lorsqu'il est nécessaire d'inspecter le contenu indexé, ce qui permet de mieux comprendre et d'analyser le comportement de la recherche. [[mw:Help:CirrusSearch/Debug|En savoir plus]]. [https://phabricator.wikimedia.org/T411169] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.16|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/08|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W08"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 février 2026 à 20:17 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30086330 --> == <span lang="en" dir="ltr">Tech News: 2026-09</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W09"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/09|Translations]] are available. '''Weekly highlight''' * [[mw:Special:MyLanguage/Edit check/Reference Check|Reference Check]] has been deployed to English Wikipedia, completing its rollout across all Wikipedias. The feature prompts newcomers to add a citation before publishing new content, helping reduce common citation-related reverts and improve verifiability. In A/B testing, the impact was substantial: newcomers shown Reference Check were approximately 2.2 times more likely to include a reference on desktop and about 17.5 times more likely on mobile web. [https://analytics.wikimedia.org/published/reports/editing/reference_check_ab_test_report_final_2025.html] '''Updates for editors''' * The [[mw:Special:MyLanguage/Extension:InterwikiSorting|InterwikiSorting extension]], which allowed for the [[m:Special:MyLanguage/Interwiki sorting order|sorting of interwiki links]], has been undeployed from Wikipedia. As a result, editors who had enabled interwiki link sorting in non-compact mode (full list format) will now see links reordered. The links moving forward will be listed in the alphabetical order of language code. [https://phabricator.wikimedia.org/T253764] * Later this week, people who are editing a page-section using the mobile visual editor, will notice a new "Edit full page" button. When tapped, you will be able to edit the entire article. This helps when the change you want to make is outside the section you initially opened. [https://phabricator.wikimedia.org/T387175][https://phabricator.wikimedia.org/T409112] * [[mw:Special:MyLanguage/Readers/Reader Experience|The Reader Experience team]] is inviting editors to assess whether dark mode should still be considered "beta" on their wiki, based on their experience of how well it functions on desktop and mobile. If the feature is deemed mature, editors can update the interface messages in <code dir=ltr>MediaWiki:skin-theme-description</code> and <code dir=ltr>MediaWiki:Vector-night-mode-beta-tag</code> to indicate that dark mode is ready and no longer considered beta. * The improved [[mw:Wikimedia_Apps/Team/iOS/Activity_Tab|Activity tab]] which displays user-insights is now available to all users of the Wikipedia iOS app (version 7.9.0 and later). Following earlier A/B testing that showed higher account creation among users with access to the feature, it has been rolled out to 100% of users along with some updates. The Activity tab now shows your edited articles in the timeline, offers editing impact insights like contribution counts and article view trends, and customization options to improve in-app experience for users. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:21}} community-submitted {{PLURAL:21|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, a bug that prevented [[mw:Special:MyLanguage/Extension:DiscussionTools|DiscussionTools]] from working on mobile has now been fixed, restoring full functionality. [https://phabricator.wikimedia.org/T415303] '''Updates for technical contributors''' * The [[m:Special:GlobalWatchlist|Global Watchlist]] lets you view your watchlists from multiple wikis on one page. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] that makes this possible continues to improve. The latest upgrade is the inclusion of a [[mw:Extension:GlobalWatchlist#hook|new hook]], <code dir=ltr>ext.globalwatchlist.rebuild</code>, which fires after each watchlist rebuild. This allows you to run gadgets and user scripts for the Special page. [https://phabricator.wikimedia.org/T275159] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.17|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/09|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W09"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 février 2026 à 20:03 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30119102 --> == Actualités techniques n° 2026-10 == <section begin="technews-2026-W10"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/10|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Le [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments|mode Anniversaire]] Wikipedia 25 est maintenant disponible sur Wikipédia en français, anglais, betawi, breton, chinois, espagnol, gorontalo, indonésien, italien, luxembourgeois, madurais, néerlandais, sicilien, tchèque, thaï et vietnamien ! Cette campagne à temps limitée célèbre 25 ans de Wikipédia avec une mascotte : « Baby Globe », disponible sous la forme d'un réglage. Lorsque ce réglage est activé, Baby Globe est montrée sur [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments/article configuration|environ 2 500 articles]], attendant d'être découverte par des lecteurs. Chaque communauté peut choisir d'activer le mode Anniversaire par consensus et en demandant à un administrateur de le rendre disponible et de le personaliser via une [[m:Special:MyLanguage/Wikipedia 25/Easter egg experiments#Community Configuration Demo|configuration]] sur le wiki local. '''Actualités pour la contribution''' * Le [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|sous-référencement]], une nouvelle fonctionalité pour réutiliser des références avec des détails différents est maintenant disponible sur Wikipédia en suédois, polonais et [[:phab:T418209|quelques autres]]. Vous pouvez [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#test|essayer la fonctionalité]] sur ces projets ou sur testwiki et [https://en.wikipedia.beta.wmcloud.org/wiki/Sub-referencing betawiki]. Les retours des premiers essais sur Wikipédia en allemand ont été [[:m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing/Learnings|publiés dans un rapport]]. Contactez l'équipe de Wikimédia Allemagne si vous êtes [[:m:Talk:WMDE Technical Wishes/Sub-referencing#Pilot wikis|intéressés pour devenir un wiki pilote]]. * La [[mw:Special:MyLanguage/Help:Edit check#Paste check|vérification du collage clavier]] sera disponible sur tous les Wikipédias cette semaine. Cette fonctionalité avertit les nouveaux contributeurs qui collent du texte qu'ils n'ont probablement pas écrit de vérifier si laisser celui-ci risque de causer une violation du droit d'auteur. La vérification du collage clavier [[mw:Special:MyLanguage/Edit check/Tags|marque]] toutes les modifications où l'avertissement a été montré pour permettre leur vérification. Les administrateurs locaux peuvent configurer les différents aspects de cette fonctionalité à travers [[{{#special:EditChecks}}]]. Des [[mw:Special:MyLanguage/Edit check/Paste Check#A/B Experiment|études]] sur 22 wikis ont montré que cette vérification permet une réduction de 18% des annulations comparé au groupe de contrôle. Les traducteurs peuvent [https://translatewiki.net/w/i.php?title=Special%3ATranslate&group=ext-visualeditor-ve-mw-editcheck&filter=&optional=1&action=translate aider à traduire] cette fonctionalité. * <span lang="en" dir="ltr" class="mw-content-ltr">The [[mw:Special:MyLanguage/Readers/Reader Experience|Reader Experience team]] will be standardizing the user menu in the top right for all mobile users so that it is closer to the desktop experience. Currently this user menu is only visible to users with Advanced Mobile Controls (AMC) turned on. The only change is that a couple buttons previously in the left-side menu will move to the top right for users who do not have AMC turned on. This change is expected to go out March 9 and seeks to improve the user interface.</span> [https://phabricator.wikimedia.org/T413912] * À partir de la semaine du 2 mars, les emails envoyés lorsqu'une adresse email a été ajoutée, supprimée ou changée pour un compte changera pour adopter un formattage HTML beaucoup plus agréable et plus clair que le texte brut précédent. [https://phabricator.wikimedia.org/T410807] * Les notifications sont actuellement limitées à 2 000 entrées historiques par utilisateur et remontent à 2013 lorsque la fonctionnalité a été publiée. Le système va être modifié pour ne stocker que les notifications des 5 dernières années, mais jusqu'à 10 000 d'entre elles. Cela contribuera à la santé à long terme des infrastructures et à empêcher que les notifications plus récentes disparaissent trop tôt. [https://phabricator.wikimedia.org/T383948] * <span lang="en" dir="ltr" class="mw-content-ltr">The [[m:Special:GlobalWatchlist|Global Watchlist]] which lets you view your watchlists from multiple wikis on a single page continues to see improvements. The latest update improves label usage experience. The [[mw:Special:MyLanguage/Extension:GlobalWatchlist|extension]] now allows activating the [[mw:Special:MyLanguage/Manual:Language#Fallback languages|language fallback system]] for Wikidata items without labels in the viewed language, and showing those labels in the user’s preferred Wikidata language if no <code dir=ltr>uselang=</code> URL parameter is provided.</span> [https://phabricator.wikimedia.org/T373686][https://phabricator.wikimedia.org/T416111] * L'équipe Wikipédia Android a commencé un test beta de la [[mw:Special:MyLanguage/Readers/Information Retrieval/Phase 1|recherche hybride]] sur Wikipédia en grec. Cette recherche hybride supporte les requêtes sémantique et par mot clés, permettant aux utilisateurs de trouver ce qu'ils cherchent plus facilement. * Pour des raisons de sécurité, les membres de certains groupes sont [[m:Special:MyLanguage/Mandatory two-factor authentication for users with some extended rights|forcés d'avoir la double authentification]] (A2F) d'activée. Actuellement, l'A2F n'est nécessaire que pour utiliser les droits du groupe, et non pour en faire partie. Vu que ce système admet certaines failles, il sera [[phab:T418580|changé graduellement en mars]]. Les membres de ces groupes ne pourront plus désactiver la dernière méthose d'A2F sur leur compte, et il sera impossible d'ajouter des utilisateurs sans A2F à ces groupes. Il sera toujours possible de rajouter d'autres méthodes d'authentification et d'en enlever, tant qu'une est toujours activée. Dans la seconde moitié de mars, les utilisateurs sans A2F seront retirés de ces groupes. Cela s'applique aux administrateurs CentralNotice, aux vérificateurs d'utilisateurs, aux administrateurs d'interface, aux masqueurs, aux staff de Wikidata et Wikifonctions ainsi qu'aux bureaux IT et Confiance et sécurité de la WMF. Rien ne changera pour les autres utilisateurs. Voir la tâche liée pour le calendrier de déploiement. [https://phabricator.wikimedia.org/T418580] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:27|la tâche soumise|les {{formatnum:27}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:27||s}} la semaine dernière]]. Par exemple, le problème empêchant les utilisateurs de créer une instance dans [https://www.wikibase.cloud/ Wikibase.cloud] a maintenant été résolu. [https://phabricator.wikimedia.org/T416807] '''Actualités pour la contribution technique''' * <span lang="en" dir="ltr" class="mw-content-ltr">To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], over the next month the Wikimedia Foundation will implement global API rate limits across our APIs. In early March, stricter limits will be applied to unidentified requests from outside Toolforge/WMCS and API requests that are made from web browsers. In April, higher limits will be applied to identified traffic. These limits are intentionally set as high as possible to minimise impact on the community. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]].</span> * <span lang="en" dir="ltr" class="mw-content-ltr">The Wikidata Query Service Linked Data Fragment (LDF) endpoint will be decommissioned in February. This endpoint served limited traffic, which was successfully migrated to other data access methods that were better suited to support existing use cases. The hardware used to support the LDF endpoint will be reallocated to support the ongoing backend migration efforts.</span> [https://phabricator.wikimedia.org/T415696] * Le nouvel analyseur syntaxique Parsoid [[mw:Special:MyLanguage/Parsoid/Parser Unification/Updates|continue d'être déployés sur plus de wikis]], améliorant la pérennité de la platforme et rendant plus facile l'ajout de nouvelles fonctionalités de lecture et de modification. Parsoid est maintenant l'analyseur par défaut sur 488 wikis de la WMF (268 Wikipédias), couvrant plus de 10% de toutes les lectures de pages Wikipédia. * Le processus et les critères pour [[Special:MyLanguage/Wikimedia Enterprise#Access|demander un accès exceptionnel]] au flux à fort volume de l'API ''Wikimédia Entreprise'' (sans coût pour des utilisations en rapport à notre mission) [[m:Talk:Wikimedia Enterprise#Exceptional access criteria|ont maintenant été publiés]]. Notre but est de donner une documentation plus claire et plus complète aux utilisateurs. * [https://techblog.wikimedia.org/ Le blog Tech], dédié à la communité technique de Wikimédia [https://techblog.wikimedia.org/2026/02/24/a-tech-blog-diff/ va migrer] vers [[diffblog:|Diff]], le blog pour les nouvelles et événements de la communauté. La migration devrait être terminée en Avril 2026, après quoi les nouveaux posts seront acceptés pour être publiés. Les lecteurs pourront lire les posts - anciens ou nouveaux - sur https://diff.wikimedia.org/. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.18|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/10|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W10"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 2 mars 2026 à 18:51 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30137798 --> == <span lang="en" dir="ltr">Tech News: 2026-11</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W11"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/11|Translations]] are available. '''Weekly highlight''' * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. * Last week, all wikis had 2 hours of read-only time, and extended unavailability for user-scripts and gadgets. This was due to a security incident which has since been resolved. Work is ongoing to prevent re-occurrences. For current information please see the [[m:Steward's noticeboard#Statement on Meta about today's user script security incident|post on the Stewards' noticeboard]] ([[m:Special:MyLanguage/Wikimedia Foundation/Product and Technology/Product Safety and Integrity/March 2026 User Script Incident|translations]]). '''Updates for editors''' * Users facing multiple blocks on mobile will now see the reasons for each block separately, instead of a generic message. This helps them understand why they are blocked and what steps they can take to resolve the issue. For example, users affected for using common VPNs (such as [[Special:MyLanguage/Apple iCloud Private Relay|iCloud Private Relay]]) will receive clearer guidance on what they need to do to start editing again. [https://phabricator.wikimedia.org/T357118] * Later this week, [[mw:Special:MyLanguage/VisualEditor/Suggestion Mode|Suggestion Mode]] will become available as a beta feature within the visual editor at all Wikipedias. This feature proactively suggests various types of actions that people can consider taking to improve Wikipedia articles, and learn about related guidelines. The feature is locally configurable, and can also be locally expanded with custom Suggestions. Current settings can be seen at [[Special:EditChecks]] and there are [[mw:Special:MyLanguage/Help:Suggestion mode#For administrators %E2%80%93 local customization|instructions for how administrators can customize]] the links to point to local guidelines. The feature is connected to [[mw:Special:MyLanguage/Help:Edit check|Edit check]] which suggests improvements while someone is writing new content. In the future, the Editing team plans to evaluate the feature's impact with newcomers through a controlled experiment. [https://phabricator.wikimedia.org/T404600] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:23}} community-submitted {{PLURAL:23|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where the cursor became misaligned during the use of CodeMirror’s syntax highlighting, which makes wikitext and code easier to read, has now been fixed. This problem specifically affected users who defined a font rule in a custom stylesheet while creating a new topic with DiscussionTools. [https://phabricator.wikimedia.org/T418793] '''Updates for technical contributors''' * API rate limiting update: To help ensure [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|fair use of infrastructure]], global API rate limits will be applied this week to requests without a compliant User-Agent that originate from outside Toolforge/WMCS and to unauthenticated requests made from web browsers. Higher limits will be applied to identified traffic in April. Bots running in Toolforge/WMCS or with the bot user right on any wiki should not be affected for now. However, all developers are advised to follow updated best practices. For more information, see [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|Wikimedia APIs/Rate limits]]. * The new GraphQL API has been released. The API was developed as a flexible alternative to select features of the Wikidata Query Service (WDQS), to improve developer experience and foster adaptability, and efficient data access. Try it out and [[d:Wikidata:Wikibase GraphQL#Feedback and development|give feedback]]. You can also [https://greatquestion.co/wikimediadeutschland/GraphQLAPI/apply sign up for usability tests]. * The [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group|PTAC Unsupported Tools Working Group]] continued improvements to [[commons:Special:MyLanguage/Commons:Video2commons#|Video2Commons]] in February, with fixes addressing authentication errors, large-file handling, task queue visibility, and clearer upload behavior. Work is still ongoing in some areas, including changes related to deprecated server-side uploads. Read [[m:Special:MyLanguage/Product and Technology Advisory Council/Unsupported Tools Working Group#February 2026|this update]] to learn more. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.19|MediaWiki]] '''In depth''' * The Article Guidance team invites experienced Wikipedia editors from selected [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators#Collaborators|pilot wikis]] and interested contributors from other Wikipedias to fill out this questionnaire which is available in [https://docs.google.com/forms/d/e/1FAIpQLSfmLeVWnxmsCbPoI_UF2jyRcn73WRGWCVPHzerXb4Cz97X_Ag/viewform English], [https://docs.google.com/forms/d/e/1FAIpQLSd6rzr4XXQw8r4024fE3geTPFe13M_6w7Mitj-YJi0sOlWTAw/viewform?usp=header Arabic], [https://docs.google.com/forms/d/e/1FAIpQLSdok3-RfB18lcugYTUMGkpwmqG_8p760Wv4dCXitOXOszjUDw/viewform?usp=header Bengali], [https://docs.google.com/forms/d/e/1FAIpQLSfjTfYp4jEo0akA4B1e-Nfg3QZPCudUjhJzHzzDi6AHyAaMGA/viewform?usp=header Japanese], [https://docs.google.com/forms/d/e/1FAIpQLScteVoI29Aue4xc72dekk-6RYtvmMgQxzMI900UOawrFrSTWg/viewform?usp=header Portuguese], [https://docs.google.com/forms/d/e/1FAIpQLSetdxnYwL3ub2vqA7awCg5hJZPMIYcDPaiTe12rY9h0GYnVlw/viewform?usp=header Persian], and [https://docs.google.com/forms/d/e/1FAIpQLScNvfJF-Ot-4pzA4qAN771_0QDJ4Li19YcUsaTgSKW8Nc7U_Q/viewform?usp=header Turkish]. Your answers will help the team customize guidance for less experienced editors and help them learn community policies and practices while creating an article. Learn more [[mw:Special:MyLanguage/Article guidance|on the project page]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/11|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W11"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 9 mars 2026 à 19:52 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30213008 --> == <span lang="en" dir="ltr">Tech News: 2026-12</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W12"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/12|Translations]] are available. '''Updates for editors''' * The [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature, also known as [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], has been used for wikitext syntax highlighting since November 2024. It will be promoted out of beta by May 2026 in order to bring improvements and new [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Features|features]] to all editors who use the standard syntax highlighter. If you have any questions or concerns about promoting the feature out of beta, [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|please share]]. [https://phabricator.wikimedia.org/T259059] * Some changes to local user groups are performed by stewards on Meta-Wiki and logged there only. Now, interwiki rights changes will be logged both on Meta-Wiki and the wiki of the target user to make it easier to access a full record of user's rights changes on a local wiki. Past log entries for such changes will be backfilled in the coming weeks. [https://phabricator.wikimedia.org/T6055] * On wikis using [[m:Special:MyLanguage/Flagged Revisions|Flagged Revisions]], the number of pending changes shown on [[{{#Special:PendingChanges}}]] previously counted pages which were no longer pending review, because they have been removed from the system without being reviewed, e.g. due to being deleted, moved to a different namespace, or due to wiki configuration changes. The count will be correct now. On some wikis the number shown will be much smaller than before. There should be no change to the list of pages itself. [https://phabricator.wikimedia.org/T413016] * Wikifunctions composition language has been rewritten, resulting in a new version of the language. This change aims to increase service stability by reducing the orchestrator's memory consumption. This rewrite also enables substantial latency reduction, code simplification, and better abstractions, which will open the door to later feature additions. Read more about [[f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-11|the changes]]. * Users can now sort search results alphabetically by page title. The update gives an additional option to finding pages more easily and quickly. Previously, results could be sorted by Edit date, Creation date, or Relevance. To use the new option, open 'Advanced Search' on the search results page and select 'Alphabetically' under 'Sorting Order'. [https://phabricator.wikimedia.org/T403775] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented UploadWizard on Wikimedia Commons from importing files from Flickr has now been fixed. [https://phabricator.wikimedia.org/T419263] '''Updates for technical contributors''' * A new special page, [[{{#special:LintTemplateErrors}}]], has been created to list transcluded pages that are flagged as containing lint errors to help users discover them easily. The list is sorted by the number of transclusions with errors. For example: [[{{#special:LintTemplateErrors}}/night-mode-unaware-background-color]]. [https://phabricator.wikimedia.org/T170874] * Users of the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature have been using [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] for syntax highlighting when editing JavaScript, CSS, JSON, Vue and Lua content pages, for some time now. Along with promoting CodeMirror 6 out of beta, the plan is to replace CodeEditor as the standard editor for these content models by May 2026. [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror|Feedback or concerns are welcome]]. [https://phabricator.wikimedia.org/T419332] * The [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] JavaScript modules will soon be upgraded to CodeMirror 6. Leading up to the upgrade, loading the <code dir=ltr>ext.CodeMirror</code> or <code dir=ltr>ext.CodeMirror.lib</code> modules from gadgets and user scripts was deprecated in July 2025. The use of the <code dir=ltr>ext.CodeMirror.switch</code> hook was also deprecated in March 2025. Contributors can now make their scripts or gadgets compatible with CodeMirror 6. See the [[mw:Special:MyLanguage/Extension:CodeMirror#Gadgets and user scripts|migration guide]] for more information. [https://phabricator.wikimedia.org/T373720] * The MediaWiki Interfaces team is expanding coverage of REST API module definitions to include [[mw:Special:MyLanguage/API:REST API/Extensions|extension APIs]]. REST API modules are groups of related endpoints that can be independently managed and versioned. Modules now exist for [https://phabricator.wikimedia.org/T414470 GrowthExperiments] and [https://phabricator.wikimedia.org/T419053 Wikifunctions] APIs. As we migrate extension APIs to this structure, documentation will move out of the main MediaWiki OpenAPI spec and REST Sandbox view, and will instead be accessible via module-specific options in the dropdown on the [https://test.wikipedia.org/wiki/Special:RestSandbox REST Sandbox] (i.e., [[{{#Special:RestSandbox}}]], available on all wiki projects). * The [[mw:Special:MyLanguage/Extension:Scribunto|Scribunto]] extension provides different pieces of information about the wiki where the module is being used via the [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual|mw.site]] library. Starting last week, the library also provides a [[mw:Special:MyLanguage/Extension:Scribunto/Lua reference manual#mw.site.wikiId|way]] of accessing the [[mw:Special:MyLanguage/Manual:Wiki ID|wiki ID]] that can be used to facilitate cross-wiki module maintenance. [https://phabricator.wikimedia.org/T146616] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.20|MediaWiki]] '''In depth''' * The [[m:Special:MyLanguage/Coolest Tool Award|2026 Coolest Tool Award]] celebrating outstanding community tools, is now open for nominations! Nominate your favorite tool using the [https://wikimediafoundation.limesurvey.net/435684?lang=en nomination survey] form by 23 March 2026. For more information on privacy and data handling, please see the [[foundation:Special:MyLanguage/Legal:Coolest_Tool_Award_2026_Survey_Privacy_Statement|survey privacy statement]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/12|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W12"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 16 mars 2026 à 20:35 (CET) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30260505 --> == <span lang="en" dir="ltr">Upcoming deployment of CampaignEvents extension to Wikibooks</span> == <div lang="en" dir="ltr"> <section begin="message"/> Hello everyone, We are writing to inform you that the [[mw:Help:Extension:CampaignEvents|CampaignEvents extension]] will be deployed to all Wikibooks projects during the week of '''23 March 2026'''. This follows last year’s broader rollout across Wikimedia projects. We realized that Wikibooks was not included at the time, and we’re now addressing that to ensure consistency across all communities. The CampaignEvents extension provides tools to support event and campaign organization on-wiki, including features like on-wiki event registration and collaboration lists(global event list). We welcome any questions, feedback, or concerns you may have. We are also happy to support anyone interested in trying out the tools. ''Apologies if this message is not in your preferred language. If you’re able to help translate it for your community, please feel free to do so.'' <section end="message"/> </div> <bdi lang="en" dir="ltr">[[User:Udehb-WMF|Udehb-WMF]] ([[User talk:Udehb-WMF|discussion]]) 19 mars 2026 à 19:22 (CET)</bdi> <!-- Message envoyé par User:Udehb-WMF@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=User:Udehb-WMF/sandbox/MM_target&oldid=30284073 --> == <span lang="en" dir="ltr">Tech News: 2026-13</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W13"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/13|Translations]] are available. '''Weekly highlight''' * Wikimedia site users can now log in without a password using passkeys. This is a secure method supported by fingerprint, facial recognition, or PIN. With this change, all users who opt for passwordless login will find it easier, faster, and more secure to log in to their accounts using any device. The new passkey login option currently appears as an autofill suggestion in the username field. An additional [[phab:T417120|"Log in with passkey" button]] will soon be available for users who have already registered a passkey. This update will improve security and user experience. The [[c:File:Passwordless_login_screencast.webm|screen recording]] demonstrates the passwordless login process step by step. * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 25 March 2026 at [https://zonestamp.toolforge.org/1774450800 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. '''Updates for editors''' * Wikimedia site users can now export their notifications older than 5 years using a [[toolforge:echo-chamber|new Toolforge tool]]. This will ensure that users retain their important notifications and avoid them being lost based on the planned change to delete notifications older than 5 years, as previously announced. [https://phabricator.wikimedia.org/T383948] * Wikipedia editors in Indonesian, Thai, Turkish, and Simple English now have access to Special:PersonalDashboard. This is an [[mw:Special:MyLanguage/Moderator Tools/Dashboard|early version of an experience]] that introduces newer editors to patrolling workflows, making it easier for them to move from making edits to participating in more advanced moderation work on their project. [https://phabricator.wikimedia.org/T402647] * The [[Special:Block]] now has two minor interface changes. Administrators can now easily perform indefinite blocks through a dedicated radio button in the expiry section. Also, choosing an indefinite expiry provides a different set of common reasons to select from, which can be changed at: [[MediaWiki:Ipbreason-indef-dropdown]]. [https://phabricator.wikimedia.org/T401823] * Mobile editors [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#Logged-out|at several wikis]] can now see an improved logged-out edit warning, thanks to the recent updates from the Growth team. These changes released last week are part of ongoing efforts and tests to enhance [[mw:Special:MyLanguage/Contributors/Account Creation Experiments|account creation experience on mobile]] and then increase participation. [https://phabricator.wikimedia.org/T408484] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:36}} community-submitted {{PLURAL:36|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the bug that prevented mobile web users from seeing the block information when affected by multiple blocks has been fixed. They can now see messages of all the blocks currently affecting them when they access Wikipedia. '''Updates for technical contributors''' * Images built using Toolforge will soon get the upgraded buildpacks version, bringing support for newer language versions and other upstream improvements and fixes. If you use Toolforge Build Service, review the recent [https://lists.wikimedia.org/hyperkitty/list/cloud-announce@lists.wikimedia.org/thread/EMYTA32EV2V5SQ2JIEOD2CL66YFIZEKV/ cloud-announce email] and update your build configuration as necessary to ensure your tools are compatible. [https://wikitech.wikimedia.org/w/index.php?title=Help:Toolforge/Building_container_images&oldid=2392097#Buildpack_environment_upgrade_process][https://phabricator.wikimedia.org/T380127] * The [https://api.wikimedia.org/wiki/Main_Page API Portal] documentation wiki will shut down in June 2026. API keys created on the API Portal will continue to work normally. api.wikimedia.org endpoints will be deprecated gradually starting in July 2026. Documentation on the API Portal is moving to [[mw:Wikimedia APIs|mediawiki.org]]. Learn more on the [[wikitech:API Portal/Deprecation|project page]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.21|MediaWiki]] '''In depth''' * [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] is considering improvements to [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|automatically generated reference names in VisualEditor]]. Please check out the [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names#Proposed solutions|proposed solutions]] and participate in the [[m:Talk:WMDE Technical Wishes/References/VisualEditor automatic reference names#Request for comment|request for comment]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/13|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W13"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 23 mars 2026 à 17:51 (CET) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30268305 --> == Actualités techniques n° 2026-14 == <section begin="technews-2026-W14"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/14|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Le version Beta de [[abstract:|Abstract Wikipedia]], un nouveau projet Wikimédia indépendant du langage, a été lancée la semaine dernière. Ce projet permet aux communautés de construire des articles Wikipédia dans leur langue natale, qui peuvent directement être lus par les autres utilisateurs et utilisatrices dans leur propre langage. Le wiki fonctionne grâce à des instructions de Wikifunctions et au contenu structuré issu de Wikidata. [[:f:Special:MyLanguage/Wikifunctions:Status updates/2026-03-26|En savoir plus]]. '''Actualités pour la contribution''' * L'équipe Croissance mène un test A/B afin d'évaluer l'effet d'un message plus clair et plus convivial encourageant à la création de comptes sur les wikis. Actuellement, lorsqu'un utilisateur mobile non connecté lance la modification, un message d'avertissement s'affiche, pouvant paraître abrupt et décourageant. Il présente également la modification par compte temporaire comme option par défaut, au lieu d'inciter à la création d'un compte. Le test est mené sur dix Wikipédia, dont les versions en arabe, français, espagnol et allemand. [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#2. Improve logged-out warning message (T415160)|En savoir plus]]. * L'équipe des applications Wikimédia sollicite vos commentaires sur [[mw:Special:MyLanguage/Wikimedia Apps/Team/Future of Editing on the Mobile Apps|comment devrait fonctionner l'édition dans les applications mobiles Wikipédia]]. La discussion porte sur l'amélioration de l'accès aux outils d'édition lorsque les utilisateurs appuient sur « Modifier ». Cette initiative s'inscrit dans un effort plus large visant à offrir aux lecteurs intéressés par la contribution une expérience utilisateur plus intuitive. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:45|la tâche soumise|les {{formatnum:45}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:45||s}} la semaine dernière]]. Par exemple, un problème avec la récupération de citations à partir du site d'archive de journaux [https://www.newspapers.com Newspapers.com], qui ne fonctionnait plus en raison d'un blocage des requêtes [[mw:Special:MyLanguage/Citoid|Citoid]], a maintenant été résolu. [https://phabricator.wikimedia.org/T419903] '''Actualités pour la contribution technique''' * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.22|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/14|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W14"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 30 mars 2026 à 21:25 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30329462 --> == Action Required: Update templates/modules for electoral maps (Migrating from P1846 to P14226) == Hello everyone, This is a notice regarding an ongoing data migration on Wikidata that may affect your election-related templates and Lua modules (such as <code>Module:Itemgroup/list</code>). '''The Change:'''<br /> Currently, many templates pull electoral maps from Wikidata using the property [[:d:Property:P1846|P1846]], combined with the qualifier [[:d:Property:P180|P180]]: [[:d:Q19571328|Q19571328]]. We are migrating this data (across roughly 4,000 items) to a newly created, dedicated property: '''[[:d:Property:P14226|P14226]]'''. '''What You Need To Do:'''<br /> To ensure your templates and infoboxes do not break or lose their maps, please update your local code to fetch data from [[:d:Property:P14226|P14226]] instead of the old [[:d:Property:P1846|P1846]] + [[:d:Property:P180|P180]] structure. A [[m:Wikidata/Property Migration: P1846 to P14226/List|list of pages]] was generated using Wikimedia Global Search. '''Deadline:'''<br /> We are temporarily retaining the old data on [[:d:Property:P1846|P1846]] to allow for a smooth transition. However, to complete the data cleanup on Wikidata, the old [[:d:Property:P1846|P1846]] statements will be removed after '''May 1, 2026'''. Please update your modules and templates before this date to prevent any disruption to your wiki's election articles. Let us know if you have any questions or need assistance with the query logic. Thank you for your help! [[User:ZI Jony|ZI Jony]] using [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 3 avril 2026 à 19:11 (CEST) <!-- Message envoyé par User:ZI Jony@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=29941252 --> == Actualités techniques n° 2026-15 == <section begin="technews-2026-W15"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/15|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * L’[[mw:Special:MyLanguage/Help:Extension:CampaignEvents|extension CampaignEvents]] comprend désormais une nouvelle fonctionnalité de définition d’objectifs de groupe, permettant aux organisateurs de définir et de suivre les objectifs de l’événement, tels que le nombre d’articles créés et de contributeurs participants en temps réel. De même, les participants peuvent travailler vers des cibles communes et voir leur impact collectif au fur et à mesure que l’événement se déroule. Cette fonctionnalité est désormais disponible sur tous les wikis Wikimedia. Pour en savoir plus, consultez [[mw:Special:MyLanguage/Help:Extension:CampaignEvents/Registration/Collaborative contributions#Goal setting|la documentation]]. * [[File:Maki-gift-15.svg|12px|link=|class=skin-invert|Concerne un souhait]] La nouvelle fonctionnalité d'[[mw:Special:MyLanguage/Help:Watchlist labels|étiquettes de liste de suivi]] (annoncée dans les [[m:Special:MyLanguage/Tech/News/2026/07|Actualités techniques 2026-07 ]]) est désormais disponible via l'ÉditeurVisuel, l'éditeur de code et l'«étoile de suivi»(ou le lien de suivi, pour les habillages qui n'ont pas d'icône d'étoile). Auparavant, il n'était possible d'attribuer des étiquettes que via [[Special:EditWatchlist|Modifier la liste de suivi]]. Dans ces trois emplacements, il s'agit d'un nouveau champ situé après le champ d'expiration. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:23|la tâche soumise|les {{formatnum:23}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:23||s}} la semaine dernière]]. Par exemple, le problème où les pages de discussion sur mobile avec Parsoid sont inutilisables après les en-têtes de section vides, a maintenant été résolu. [https://phabricator.wikimedia.org/T419171] '''Actualités pour la contribution technique''' * La [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|fonctionnalité de sous-référencement]], qui permet aux contributeurs d'ajouter des détails à une référence existante sans la dupliquer, sera progressivement déployée sur [[phab:T414094|davantage de wikis]] plus tard cette année. Les wikis utilisant le gadget [[mw:Special:MyLanguage/Reference Tooltips|Reference Tooltips]] sont encouragés à mettre à jour leur version (généralement sur [[m:MediaWiki:Gadget-ReferenceTooltips.js|MediaWiki:Gadget-ReferenceTooltips.js]] comme indiqué [https://en.wikipedia.org/w/index.php?diff=1344408362 ici]) pour assurer la compatibilité. D'autres gadgets liés aux références pourraient également être affectés. [https://phabricator.wikimedia.org/T416304] * Toutes les éditions de Wikinews seront fermées et passeront en mode lecture seule le 4 mai 2026. Le contenu restera accessible, mais aucune nouvelle modification ni aucun nouvel article ne pourra être ajouté. Cette fermeture a été approuvée par le Conseil d'administration de la Fondation Wikimedia à la suite de discussions prolongées. [[m:Wikimedia Foundation Board noticeboard#Board of Trustees Approves Closure of Wikinews|En savoir plus]]. * L'[[:mw:Special:MyLanguage/API:Action API|API d'action]] a proposé plusieurs formats pour les résultats demandés. L'un d'entre eux, <bdi lang="zxx" dir="ltr"><code><nowiki>format=php</nowiki></code></bdi>, sera bientôt supprimé. Veuillez vous assurer que vos scripts ou robots utilisent le [[mw:Special:MyLanguage/API:Data formats#Output|format JSON]]. Cette suppression devrait affecter très peu de scripts et de robots. [https://phabricator.wikimedia.org/T118538] * La page [[Special:NamespaceInfo|Special:NamespaceInfo]] inclut désormais les alias d'espace de noms. Par exemple «WP» pour l'espace de noms ''Projet'' (''Wikipédia'') sur la Wikipédia en allemand. [https://phabricator.wikimedia.org/T381455] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.23|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/15|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W15"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 6 avril 2026 à 18:19 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30362761 --> == <span lang="en" dir="ltr">Tech News: 2026-16</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W16"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/16|Translations]] are available. '''Weekly highlight''' * Experienced editors are invited to [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Main_Page test] the [[mw:Special:MyLanguage/Article guidance|Article guidance]] feature, designed to help less-experienced editors create well-structured, policy-compliant Wikipedia articles. Testing instructions are [[mw:Special:MyLanguage/Article guidance/Test feature guide|available]]. Also, after reviewing [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance the outlines], please provide feedback on the [[mw:Talk:Article guidance|project talk page]]. Based on your input, the feature will be refined and transferred to the pilot Wikipedias to translate and adapt. Check out [[c:File:Article Guidance workflow demo - April 2026.webm|the video]] explaining the feature. '''Updates for editors''' * On most wikis, all autoconfirmed users can now use [[Special:ChangeContentModel|Special:ChangeContentModel]] page to [[mw:Special:MyLanguage/Help:ChangeContentModel|create new pages with custom content models]], such as mass message lists, making custom page formats more accessible. Check [[Special:ListGroupRights|Special:ListGroupRights]] for the status of your wiki. [https://phabricator.wikimedia.org/T248294] * The Growth team has launched an [[mw:Special:MyLanguage/Contributors/Account_Creation_Experiments|account creation experiment]] to evaluate whether adding an account creation button to the mobile web header increases new account registrations and encourages more mobile users to contribute to the wikis. The experiment is currently live on Hindi, Indonesian, Bengali, Thai, and Hebrew Wikipedia, and targets 10% of logged-out mobile web users. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where VisualEditor could get stuck loading on Windows devices with animations turned off, has now been fixed. [https://phabricator.wikimedia.org/T382856] '''Updates for technical contributors''' * Starting later this week, {{int:group-abusefilter}} who have the [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]] beta feature enabled will have [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] instead of [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] as the editor at [[Special:AbuseFilter|Special:AbuseFilter]]. This is part of the broader effort to make the user experience more consistent across all editors. [https://phabricator.wikimedia.org/T399673][https://phabricator.wikimedia.org/T419332] * Tools and bots that access the [[mw:Special:MyLanguage/Notifications/API|Notifications API]] (<bdi lang="zxx" dir="ltr"><code><nowiki>action=query&meta=notifications</nowiki></code></bdi>) will need to update their OAuth or BotPassword grants to also include access to private notifications. [https://phabricator.wikimedia.org/T421991] * Due to a library upgrade, listings on category pages may be displayed out of order starting on Monday, 20th April. A migration script will be run to correct this, and will take hours to days depending on the size of the wiki (up to a week for English Wikipedia). [https://phabricator.wikimedia.org/T422544] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.46/wmf.24|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/16|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W16"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 13 avril 2026 à 17:19 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30380527 --> == Actualités techniques n° 2026-17 == <section begin="technews-2026-W17"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/17|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * Après deux ans de développement, la version [[mw:Special:MyLanguage/Help:Extension:CodeMirror|{{int:codemirror-beta-feature-title}}]], également connue sous le nom de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror 6]], sortira de sa phase bêta le mardi 21 avril. Elle offrira une meilleure lisibilité du code et du wikitext, une réduction des fautes de frappe et d'autres [[mw:Special:MyLanguage/Help:Extension:CodeMirror|avantages]] à tous les utilisateurs du surligneur de syntaxe standard. Un grand merci au bénévole [https://phabricator.wikimedia.org/p/Bhsd/ Bhsd] qui a développé de nombreuses nouvelles fonctionnalités, notamment [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Code folding|le repliement de code]], [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Autocompletion|la saisie semi-automatique]] et [[mw:Special:MyLanguage/Help:Extension:CodeMirror#Linting|l'analyse statique du code]]. [https://phabricator.wikimedia.org/T259059] * Une mise à jour majeure de l'application Wikipédia pour iOS est en cours de déploiement, en restructurant l'interface pour s'harmoniser avec le tout nouveau design visuel "Liquid Glass" d'Apple. [https://apps.apple.com/us/app/wikipedia/id324715238 Télécharger la dernière version] et découvrez les nouveautés. '''Actualités pour la contribution''' * [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|Les listes de lecture]] est une fonctionnalité qui permet aux lecteurs d'enregistrer des articles dans une liste pour les lire ultérieurement. Cette fonctionnalité est actuellement en version bêta sur les Wikipédias en arabe, français, indonésien, vietnamien et chinois, et activée par défaut pour tous les nouveaux comptes sur toutes les Wikipédias. * Une expérimentation visant à étendre [[mw:Special:MyLanguage/Readers/Reader Growth/Mobile page previews|les aperçus de page au web mobile]] sera lancée la semaine du 20 avril sur les versions arabe, anglaise, française, italienne, polonaise et vietnamienne de Wikipédia. Les aperçus de page sont des fenêtres contextuelles affichant une miniature, un premier paragraphe et un lien bleu permettant d'ouvrir l'article complet, facilitant ainsi la découverte de contenu. Cette fonctionnalité est déjà disponible sur ordinateur et dans les applications. [[m:Special:MyLanguage/List of experiments in Product and Technology#Template|En savoir plus sur cette expérimentation et d'autres]]. * Sur plusieurs wikis, les contributeurs connectés qui n'ont pas [[mw:Special:MyLanguage/Help:Email confirmation|confirmé leur adresse électronique]] peuvent désormais voir une bannière les invitant à le faire. La confirmation de l'adresse électronique permet à un utilisateur de récupérer l'accès à son compte en cas de perte. [[mw:Special:MyLanguage/Product Safety and Integrity/Account Security#Encouraging users to confirm their email addresses|En savoir plus]]. [https://phabricator.wikimedia.org/T421366] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:15|la tâche soumise|les {{formatnum:15}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:15||s}} la semaine dernière]]. Par exemple, un problème qui entraînait des ralentissements lors de la modification de très grandes pages wiki dans l'éditeur wikitext de 2017, des problèmes de chargement, de prévisualisation et de défilement, ainsi que des problèmes de performance lors de la sélection, de la découpe ou du collage de contenu, a maintenant été résolu. [https://phabricator.wikimedia.org/T184857] '''Actualités pour la contribution technique''' * Dans le cadre de la promotion de [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror]] à partir d'une fonctionnalité bêta, tous les utilisateurs se serviront de [[mw:Special:MyLanguage/Extension:CodeMirror|CodeMirror]] au lieu de [[mw:Special:MyLanguage/Extension:CodeEditor|CodeEditor]] pour la coloration syntaxique lors de l'édition de pages de contenu JavaScript, CSS, JSON, Vue et Lua. [https://phabricator.wikimedia.org/T419332] * <span class="mw-translate-fuzzy">Le service <code>mirrors.wikimedia.org</code> pour les utilisateurs de Debian et Ubuntu sera définitivement arrêté le 15 mai. Le matériel du serveur sera remplacé par des solutions plus performantes. Certains utilisateurs devront peut-être migrer vers un autre serveur qui ne devra prendre qu'une minute. [https://lists.wikimedia.org/hyperkitty/list/wikitech-l@lists.wikimedia.org/thread/LJYRIS4WB66HIRCAO4GIDTXCMDVZRBMA/ Vous pouvez en savoir plus].</span> [https://phabricator.wikimedia.org/T416707] * Les tables <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi> seront supprimées de [[wikitech:Help:Wiki Replicas|wikireplicas]]. Si vos outils ou requêtes accèdent directement à <bdi lang="zxx" dir="ltr"><code><nowiki>image</nowiki></code></bdi> ou <bdi lang="zxx" dir="ltr"><code><nowiki>oldimage</nowiki></code></bdi>, veuillez les mettre à jour pour utiliser les tables <bdi lang="zxx" dir="ltr"><code><nowiki>file</nowiki></code></bdi> et <bdi lang="zxx" dir="ltr"><code><nowiki>filerevision</nowiki></code></bdi> avant le 28 mai. [https://phabricator.wikimedia.org/T28741] * Suite à la récente mise en place de limites de débit globales pour les API non identifiées, la Fondation Wikimedia poursuit ses efforts pour garantir [[mw:Special:MyLanguage/MediaWiki Product Insights/Responsible Reuse|une utilisation équitable de l'infrastructure]] en appliquant des limites globales au trafic des API identifiées à partir de la dernière semaine d'avril. Ces limites sont volontairement fixées au niveau le plus élevé possible afin de minimiser l'impact sur la communauté. Les bots exécutés dans Toolforge/WMCS ou disposant des droits d'utilisateur de bot sur un wiki ne devraient pas être affectés pour le moment. Toutefois, il est conseillé à tous les développeurs de suivre les bonnes pratiques mises à jour. Pour plus d'informations, consultez la page [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|API Wikimedia/Limites de débit]] et la [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits/FAQ|Foire aux questions]]. * L'[[mw:Special:MyLanguage/Attribution API|API d'attribution]] est désormais disponible en [[mw:Special:MyLanguage/Wikimedia APIs/Stability policy|version bêta]]. Elle récupère les informations nécessaires pour créditer les articles et les fichiers multimédias de Wikimedia, quel que soit leur lieu d'utilisation. La documentation de référence est disponible sur la page dédiée au Sandbox REST, accessible sur tous les wikis Wikimedia (comme [https://en.wikipedia.org/w/index.php?api=attribution.v0-beta&title=Special%3ARestSandbox le sandbox REST de Wikipédia en anglais]). N'hésitez pas à partager vos commentaires sur la [[mw:Talk:Attribution API|page de discussion du projet]]. * Il n'y aura pas de nouvelle version de MediaWiki cette semaine. '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/17|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W17"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20 avril 2026 à 17:00 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30432763 --> == Request for comment (global AI policy) == <bdi lang="en" dir="ltr" class="mw-content-ltr"> Apologies for writing in English. {{int:Please-translate}} A [[:m:Requests for comment/Artificial intelligence policy|request for comment]] is currently being held to decide on a global AI policy. {{int:Feedback-thanks-title}} [[Utilisateur:MediaWiki message delivery|MediaWiki message delivery]] ([[Discussion utilisateur:MediaWiki message delivery|discussion]]) 26 avril 2026 à 02:57 (CEST) </bdi> <!-- Message envoyé par User:Codename Noreste@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30424282 --> == Actualités techniques n° 2026-18 == <section begin="technews-2026-W18"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/18|D’autres traductions]] sont disponibles. '''Actualités pour la contribution''' * Un changement dans la manière dont les utilisateurs et utilisatrices sont automatiquement confirmés est en cours pour améliorer la protection contre le vandalisme. Actuellement, il suffit d’avoir un compte depuis quelques jours avec quelques contributions pour être ajouté au groupe [[{{int:grouppage-autoconfirmed/{{CONTENTLANGUAGE}}}}|{{int:group-autoconfirmed}}]]. Cette configuration tend à être exploitée par certains vandales qui créent des comptes et commencent à les utiliser après un certain temps. Pour réduire ce problème, la configuration va changer la semaine prochaine afin que l’âge du compte minimum pour être confirmé automatiquement ne soit calculé qu’à partir de la première modification, au lieu de la date d’inscription. L’âge minimum du compte restera le même, c’est seulement le point de départ pour calculer cet âge qui change. Ce changement ne sera déployé que sur les wikis qui nécessitent au moins une contribution pour satisfaire les conditions de confirmation automatique. [https://phabricator.wikimedia.org/T418484] * Tous les utilisateurs et utilisatrices de Wikipédia avec un nouveau compte et ceux qui ont activé l’option « activer automatiquement la plupart des fonctionnalités bêta » peuvent désormais utiliser la fonctionnalité bêta de [[mw:Special:MyLanguage/Readers/Reader Experience/WE3.3.4 Reading lists|listes de lecture]] pour enregistrer des articles à lire plus tard. Cela permet d’organiser les lectures qui nous intéressent à un endroit unique pour y accéder facilement. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:30|la tâche soumise|les {{formatnum:30}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:30||s}} la semaine dernière]]. Par exemple, le problème avec les images d’infoboite qui avaient une marge intérieure immense dans Firefox a été corrigé. [https://phabricator.wikimedia.org/T423676] '''Actualités pour la contribution technique''' * Pour rappel, la limite globale d’accès à l’API sera appliquée cette semaine pour identifier le trafic de l’API. Le but est d’aider à garantir un [[mw:MediaWiki Product Insights/Responsible Reuse|accès équitable à l’infrastructure]]. Les robots qui s’exécutent dans Toolforge ou WMCS, ou avec le droit utilisateur ''robot'' sur les wikis, ne devraient pas être affectés pour le moment. Cependant, il est conseillé à tous les développeurs et développeuses de se conformer aux nouvelles bonnes pratiques à suivre. Pour plus d’informations, notamment la limite globale d’accès effective, consultez [[mw:Wikimedia APIs/Rate limits|la page sur la limite d’accès des API de Wikimedia]] et les [[mw:Wikimedia APIs/Rate limits/FAQ|questions-réponses]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.26|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/18|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W18"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 27 avril 2026 à 20:06 (CEST) <!-- Message envoyé par User:UOzurumba (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30458046 --> == Actualités techniques n° 2026-19 == <section begin="technews-2026-W19"/><div class="plainlinks"> Dernières '''[[m:Special:MyLanguage/Tech/News|actualités techniques]]''' de la communauté technique de Wikimedia. N’hésitez pas à informer les autres utilisateurs de ces changements. Certains changements ne vous concernent pas. [[m:Special:MyLanguage/Tech/News/2026/19|D’autres traductions]] sont disponibles. '''En lumière cette semaine''' * L’équipe chargée des fonctionnalités de [[mw:Special:MyLanguage/Article guidance|Guidage des articles]] invite les contributeurs et contributrices expérimentés des [[mw:Special:MyLanguage/Article guidance/Pilot wikis and collaborators|Wikipédia pilotes]] (arabe, bangla, japonais, portugais, persan, turc, anglais simplifié, espagnol et français) à contribuer à la traduction et à l’adaptation des [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Category:Pages_using_article_guidance exemples de trames d’articles]. Ces trames guideront les contributeurs dans la création d’articles clairs, bien structurés et conformes aux règles lors de l’utilisation de [https://b24e11a4f1.catalyst.wmcloud.org/wiki/Special:NewArticle la fonctionnalité] dès son lancement en mai 2026. Des [[mw:Special:MyLanguage/Article guidance#Adapting a sample outline in a Wikipedia|instructions simples]] expliquant comment traduire et adapter ces trames sont disponibles. '''Actualités pour la contribution''' * Le [[:m:Special:MyLanguage/Product and Technology Advisory Council|Conseil consultatif sur les produits et les technologies]] a publié [[:m:Special:MyLanguage/Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|une proposition de recommandation]] d’une procédure type que les organisations affiliées à Wikimedia pourraient suivre pour contribuer au domaine technique. Les membres de la communauté sont invités à donner leur avis sur cette recommandation avant le 8 mai [[:m:Talk:Product and Technology Advisory Council/May 2026 draft PTAC recommendation for feedback|sur la page de discussion]]. * Le nombre de préférences de taille de la miniature disponibles dans MediaWiki va être réduit à trois options standardisées : ''petite'' (180 px), ''moyenne'' (250 px) et ''large'' (400 px), dans le cadre du travail en cours pour améliorer les performances et réduire la pression sur les services de miniatures. Par conséquent, les préférences existantes seront automatiquement adaptées à la nouvelle taille la plus proche (par exemple, les petites tailles comme 120 px ou 150 px s’afficheront à 180 px, tandis que les grandes tailles comme 300 px ou 360 px s’afficheront à 400 px). L’interface des préférences sera bientôt mise à jour pour refléter ces changements, et les utilisateurs qui souhaitent s’y opposer ou donner leur avis peuvent le faire. [https://phabricator.wikimedia.org/T424909] * Dorénavant, même lorsqu’une permission expire automatiquement, les utilisateurs recevront une notification Echo similaire à la notification normale pour les changements de permissions. Quant au [[m:Special:MyLanguage/Global reminder bot|robot global de rappel]], il continue de prévenir les utilisateurs une semaine ''avant'' que leurs droits ne soient sur le point d’expirer, afin qu’ils puissent les faire renouveler. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Voir {{PLURAL:32|la tâche soumise|les {{formatnum:32}} tâches soumises}} par la communauté [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|résolue{{PLURAL:32||s}} la semaine dernière]]. Par exemple, le problème du sélecteur de langue ULS dans [[m:Special:Translate|Special:Translate]] qui faisait défiler verticalement alors qu’il ne devait pas, a été résolu. Auparavant, lorsque les utilisateurs ouvraient le menu déroulant « Traduire en français » et commençaient à saisir le nom d’une langue, la boîte de dialogue défilait verticalement de quelques pixels même lorsqu'il y avait suffisamment d’espace pour afficher tous les résultats. Le menu déroulant ne se déplace plus inutilement lors du filtrage des langues. [https://phabricator.wikimedia.org/T358864] * La [[m:Special:GlobalWatchlist|liste de suivi globale]], qui vous permet de consulter vos listes de suivi provenant de plusieurs wikis sur une seule page, continue de s’améliorer. Par exemple, les listes de suivi pour les sites avec Wikibase tels que [[:d:|Wikidata]] prennent désormais en charge les éléments [[mw:Special:MyLanguage/Extension:EntitySchema|EntitySchema]] pour un meilleur suivi. Le mode Mises à jour en direct actualise désormais la page spéciale toutes les 60 secondes afin de se conformer aux [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|nouvelles limites globales d’accès à l’API]] pour une meilleure réactivité en temps réel. Par ailleurs, un bug de directionnalité du texte qui affichait les liens comme « changements 3 » au lieu de « 3 changements » dans les listes à directions mixtes a été corrigé. [https://phabricator.wikimedia.org/T415450][https://phabricator.wikimedia.org/T424422][https://phabricator.wikimedia.org/T418091] '''Actualités pour la contribution technique''' * La deuxième phase de [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits|limitations globales d’accès à l’API]] a été déployée pour réduire l’[[diffblog:2026/03/26/quo-vadis-crawlers-progress-and-whats-next-on-safeguarding-our-infrastructure/|impact des robots IA]] et assurer un accès équitable et durable aux ressources de Wikimedia, en donnant la priorité au trafic humain et conforme à notre mission. Les [[mw:Special:MyLanguage/Wikimedia APIs/Rate limits#Limits|limites]] ne s’appliquent plus par heure mais par minute, produisant une meilleure répartition dans les structures de trafic ainsi qu’une meilleure prévisibilité de la charge de l’API. Les utilisateurs de la communauté ne devraient pas être affectés, et aucune action n’est requise. Les premières indications montrent que certains requérants basés sur l'agent utilisateur ajustent leur comportement, et environ 64 % du trafic API automatisé a été identifié. La surveillance continue, et Wikimedia Enterprise reste disponible pour l’assistance commerciale. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Sujet récurrent]] Détail des mises-à-jour à venir cette semaine : [[mw:MediaWiki 1.46/wmf.27|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Actualités techniques]]''' préparées par les [[m:Special:MyLanguage/Tech/News/Writers|rédacteurs des actualités techniques]] et postées par [[m:Special:MyLanguage/User:MediaWiki message delivery|robot]]. [[m:Special:MyLanguage/Tech/News#contribute|Contribuer]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/19|Traduire]]&nbsp;• [[m:Tech|Obtenir de l’aide]]&nbsp;• [[m:Talk:Tech/News|Donner son avis]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|S’abonner ou se désabonner]].'' </div><section end="technews-2026-W19"/> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 4 mai 2026 à 22:43 (CEST) <!-- Message envoyé par User:STei (WMF)@metawiki en utilisant la liste sur https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30498077 --> qvrlhnrcy9lczpgd5x51jm2375vrlsv Le mouvement Wikimédia/Livre audio 0 83869 765956 765645 2026-05-04T14:46:22Z Lionel Scheepmans 20012 765956 wikitext text/x-wiki '''LE MOUVEMENT WIKIMÉDIA.''' '''Dernier partage altruiste de la connaissance libre ?''' De Lionel Scheepmans. Avec l'aide de la communauté Wikimédia. '''Quatrième de couverture.''' '''Le mouvement Wikimédia, l'aventure inspirante d'une organisation mondiale et altruiste, au service d'un savoir libre et vérifiable.''' Quel est ce seul acteur à but non lucratif présent dans le top 100 des sites les plus visités sur le Web ? Comment incarne-t-il l’expression la plus visible des valeurs de liberté, d’égalité et de partage, héritées de la révolution numérique et des mouvements sociaux des années 1960 ? Comment, à partir de Wikipédia et suite à la création d’une quinzaine de projets frères, distribués en centaines de versions linguistiques, le mouvement social Wikimédia a imaginé un monde dans lequel le savoir se produit et se partage librement ? Et comment, en toute autonomie, des dizaines de projets pédagogiques, édités par des millions de bénévoles, soutenus par une fondation et près de 200 associations et groupes locaux, produisent-ils la plus grande intelligence collective au monde ? Avec de nombreux codes QR, cet ouvrage répond à ces questions, tout en permettant de mieux comprendre le monde global et numérique qui nous entoure. Lionel Scheepmans est docteur en sciences politiques et sociales, militant de la culture libre et professeur d’anthropologie numérique. Il occupe plusieurs postes d’administrateur au sein du mouvement Wikimédia qu’il observe de manière participative depuis 2011. Ses travaux universitaires, du master à la thèse de doctorat, furent consacrés à l’organisation et aux enjeux de Wikipédia et du mouvement Wikimédia. '''Avant-propos''' Pour offrir un confort de lecture sur papier sans perdre la puissance du numérique, des codes QR sont affichés tout au long de cet ouvrage. À l’aide d’une tablette ou d’un smartphone, ils permettent un accès direct à ce qui serait coûteux ou impossible à imprimer. Par exemple, le code QR 1, présent à la fin de cette page, donne accès directement à la page web qui reprend l’intégralité de l’ouvrage. Une fois sur celle-ci, on peut alors visionner les illustrations en couleur ou les enregistrements qui s’y trouvent et consulter ensuite leurs pages de descriptions en cas de besoin. Cette version numérique comprend aussi de nombreux hyperliens pointant vers Wikipédia et d’autres sites du mouvement Wikimédia, où se trouvent des compléments d’informations et leurs mises à jour. Pour économiser du papier lors de l’impression, la section regroupant les notes et les références de l’ouvrage n’est disponible qu’au format numérique, mais est directement accessible via le code QR 2 repris ci-dessous. Grâce aux indices de renvoi chiffrés et placés en exposant dans le texte imprimé, il est alors possible, au départ d'un smartphone ou d'une tablette, de retrouver les notes et les références en fonction de leur numérotation. Lorsque la référence correspond à une page web, un lien pointant vers le projet Internet Archive s’y trouve repris, pour garantir un accès aux archives des pages citées dans l’ouvrage, si jamais elles avaient disparu du web. Quant aux pages toujours existantes, elles restent accessibles via leurs hyperliens originels, pour consulter leurs éventuelles évolutions. Étant donné que ce livre est produit sur une plateforme collaborative, tout le monde est invité à améliorer les prochaines versions. On peut le faire en corrigeant des fautes d’orthographe ou de syntaxe sur les pages web qui constituent les différents chapitres de l’ouvrage, ou encore en apportant des commentaires sur les pages de discussion qui leur sont associées. Cela peut se faire très simplement en cliquant sur « Modifier » , quand on est sur la page d'un chapitre, et sur «  Ajouter un sujet  » lorsque l'on est sur une page de discussion. Une page de discussion générale est aussi accessible grâce au code QR 3. Elle permet de commenter le livre dans son ensemble, ou de poser une question à son sujet. Il suffit pour cela d’indiquer un titre dans le champ « Démarrer un nouveau sujet », avant d'écrire le contenu de son message dans l’encadré situé juste en dessous, et de cliquer finalement sur le bouton « Ajouter un sujet de manière anonyme », pour publier celui-ci. Enfin, pour ceux qui voudraient agrémenter leur lecture d’un fond sonore relaxant et original, le code QR 4 donne accès à une page web qui diffuse une musique mélodieuse. Celle-ci est composée de sons spécifiquement produits chaque fois qu’une modification est apportée sur un projet Wikimédia, ou qu’un nouveau compte y est créé. Ce dispositif ingénieux offre ainsi, aux lecteurs qui le souhaitent, une ambiance sonore particulièrement confortable, ainsi qu’une nouvelle expérience immersive au sein du mouvement Wikimédia. '''Introduction : Wikimédia n’est pas Wikipédia.''' Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si l’encyclopédie libre reste l'activité phare du mouvement à ce jour, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une ethnographie du projet Wikipédia en français. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une thèse de doctorat et cinq années de recul supplémentaires furent nécessaires. À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages, réparties sur plus d’un millier de sites web, dont 358 seulement, correspondent aux versions linguistiques de Wikipédia. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi cinq autres espaces collaboratifs d'écriture susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques ; Wikiversité rassemble des supports d'enseignement et des travaux de recherche ; Wikivoyage développe un guide touristique mondial ; Wikispecies établit un répertoire du vivant, tandis que Wiktionnaire définit des mots de toutes les langues, dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées au mouvement et dispersées dans le monde. Autour de la Fondation Wikimédia chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 associations thématiques, 40 associations locales, dont Wikimedia Deutschland qui regroupe plus de 170 employés, et finalement 141 groupes d’utilisateurs et utilisatrices. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia » partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement. En janvier 2020, ces opposants ont ainsi créé une page d’appel à commentaires, qui fut le siège d’un long débat. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une lettre ouverte adressée à la Fondation, qui comprenait le paragraphe suivant. « Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir Wikipédia présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. » En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe au sein même de sa propre communauté. Comme exemple, on peut observer que l'article Wikipédia en anglais, consacré au mouvement Wikimédia, ne s’est développé qu’à partir de 2016, tandis que celui de la version francophone de l'encyclopédie, n’est apparu qu’en 2019. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement 39 d'entre elles, sur un total de 358, possédaient un article dédié au mouvement Wikimédia. Tous ces éléments justifient donc la nécessité d’offrir au monde, une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations, qui participent à sa mission de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des résolutions prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque, c’est avant tout un travail d’information et de sensibilisation à destination du public qu'il reste à faire. '''Première partie : La naissance du mouvement Wikimédia.''' Il existe dans l'espace web, une multitude d’archives permettant de retracer les événements, qui ont conduit à la naissance du mouvement Wikimédia. Cette « préhistoire » du mouvement peut notamment être explorée grâce au réseau d’éducation populaire Framasoft, dont le site est apparu environ un an avant la création de la version francophone de Wikipédia. On trouve sur cette plateforme une mine d’informations concernant les logiciels libres et la culture libre, soit deux épisodes majeurs de l’histoire de l’informatique et d’Internet, malheureusement méconnus du grand public. Grâce à Framasoft et bien d’autres associations, il est possible de découvrir l’organisation et les motivations des millions de personnes qui participent au mouvement du logiciel libre. On peut apprendre par exemple, que ce mouvement politique et social, au sein du milieu informatique, a été initié en 1983 par Richard Stallman. Programmeur du MIT à cette époque, c'est en effet lui qui fut le premier à proposer une alternative à la marchandisation du secteur informatique. La philosophie de libre partage, apparue au sein du projet de Stallman, reflétait une certaine éthique et une organisation de travail originale, développées au sein d’une sous-culture, en vogue dans le milieu informatique depuis les années 1950. Celle-ci fut documentée dans de nombreux ouvrages, dont « L’éthique hacker », un livre remarquable, dans lequel le philosophe finlandais, Pekka Himanen, analyse en détail les origines de la culture hacker. Un simple extrait de sa quatrième de couverture, repris ci-dessous, permet d’appréhender la manière de penser de ces informaticiens, rejoints par Richard Stallman durant ses études universitaires, avant d'en devenir l’une des figures les plus charismatiques. « On considérait jusqu’à présent le « hacker » comme un voyou d’Internet, responsable d’actes de piratage et de vols de numéros de cartes bancaires. Le philosophe Pekka Himanen voit au contraire les hackers comme des citoyens modèles de l’ère de l’information. Il les considère comme les véritables moteurs d’une profonde mutation sociale. Leur éthique, leur rapport au travail, au temps ou à l’argent, sont fondés sur la passion, le plaisir ou le partage. Cette éthique est radicalement opposée à l’éthique protestante, telle qu’elle est définie par Max Weber, du travail comme devoir, comme valeur en soi, une morale qui domine encore le monde aujourd’hui. » En introduisant cette première partie d'ouvrage de la sorte, nous pouvons déjà comprendre que le mouvement Wikimédia plonge ses racines dans une transition culturelle remplie d’utopie. Une utopie qui s’oppose notamment à ce que l’historien et anthropologue Karl Polanyi désignait, en 1944 déjà, comme un libéralisme économique qui « subordonne les objectifs humains à la logique d’un mécanisme de marché impersonnel ». Étape par étape et en commençant par analyser cette utopie spécifiquement au niveau du mouvement Wikimédia, voyons maintenant ce qui s'est passé tout au long de cette révolution culturelle et numérique. '''Chapitre 1 : L'utopie Wikimédia.''' Au fil du temps, Wikipédia fut perçu comme une utopie en marche, puis comme une utopie réalisée, et finalement comme la dernière utopie collective du Web. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentes pièces de ces édifices. En visitant le quartier Wikimédia, on découvrirait donc plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement, mais aussi modifiée au niveau de son contenu. On peut ainsi y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et même changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques, ou plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans les modifications, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, une personne qui surveille la pièce prendra certainement le relais pour annuler les changements malveillants, et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte et de profiter de nombreux outils, dont un système de notification qui envoie un message dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, il n'est pas nécessaire de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les adresses IP des visiteurs. Car contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les GAFAM, NATU, BATX, le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de consensus concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments Wikipédia, chacun dédié à une version linguistique de l'encyclopédie. Toujours séparés en versions linguistiques, on trouve ensuite : les bibliothèques Wikilivres et Wikisource, les bâtiments lexicaux Wiktionnaire, le journal Wikinews, le centre pédagogique et de recherche Wikiversité, le syndicat d’initiative Wikivoyage, le répertoire des êtres vivants Wikispecies et enfin l'institut des citations d’auteurs Wikiquote. Cela sans oublier le musée médiatique Wikimedia Commons et la banque Wikidata, reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que MediaWiki, Wikitech, Phabricator, qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment Méta-Wiki que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment Wikimedia VRT. À la suite de quoi, il ne reste plus qu'à citer le bâtiment Wikimedia Outreach, pour des initiatives de sensibilisation, et le bâtiment du journal Diff Wikimedia, comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont gérés exclusivement par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, on en vient donc à se demander comment tout cela fut rendu possible. Mais pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la contre-culture des années 1960 jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de mai 68 en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'Internet, comme réseau mondial de communication en libre accès, et le développement du World Wide Web, qui a grandement facilité les interactions humaines à l’échelle planétaire. Puis, ce fut l'arrivée du Web 2.0, caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les moteurs de Wiki, dont le plus puissant d’entre eux, MediaWiki, est un logiciel libre développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le mouvement du logiciel libre, qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement. '''Chapitre 2 : Le mouvement du logiciel libre.''' L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du Massachusetts Institute of Technology, appelé Richard Stallman, déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de GNU, un nouveau système d’exploitation qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel. Dans son message transmis via ARPANET, le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte. Je considère comme une règle d’or que si j’apprécie un programme, je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer d'utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’histoire du logiciel libre. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des logiciels propriétaires d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales : 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. Lors de l'apparition du logiciel libre, le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs, qui bénéficiaient d’un accès à un ordinateur, faisait l'objet d'une remise en question. Ce changement faisait notamment suite au Copyright Act de 1976, une nouvelle loi qui autorisait l'application d'un droit d'auteur sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des clauses de confidentialité ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers circuits intégrés⁣⁣, les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un bien de grande consommation. En 1982, le Commodore 64 entrait ainsi dans le livre Guinness des records, avec plus de 17 millions d’exemplaires vendus dans le monde. Mais avant cela, en 1981, l’IBM Personal Computer avait déjà fait son apparition, en proposant une architecture ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise Microsoft, créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, IBM avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour abus de position dominante et vente liée du logiciel avec le matériel informatique, mais sans pour autant empêcher Bill Gates, le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. Toutefois, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé Linus Torvalds. Via le système de messagerie Usenet, sa demande avait été postée dans une liste de diffusion consacrée au système d’exploitation Minix, une sorte d’UNIX simplifié et développé dans un but didactique, par le programmeur Andrew Tanenbaum. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre, Torvalds entama son message par le paragraphe suivant. « Je fais un système d’exploitation gratuit (juste un hobby). Il ne sera pas grand et professionnel comme gnu pour les clones 386. Ce projet est en cours depuis avril et commence à se préparer et j’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu, même disposition physique du système de fichiers pour des raisons pratiques entre autres. » Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « Linux », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du noyau Hurd, alors qu'il était censé établir la communication entre la suite logicielle produite par GNU et le matériel informatique. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé GNU/Linux. Au départ de ce nouveau système informatique, de nombreuses variantes, que l’on nomme communément « distributions », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule Debian et tire sa réputation d'être simultanément libre, gratuite, très fiable et produite par une communauté sans lien direct avec une société commerciale. Quatre qualités qui expliquent pourquoi, Debian sert de base à plus de 150 distributions dérivées, et que de nombreuses organisations l'utilisent, comme le fait la Fondation Wikimédia sur les serveurs qui hébergent les projets qu'elle supporte. Grâce à la naissance des logiciels libres, le mouvement Wikimédia a donc la possibilité de faire tourner ses serveurs informatiques, avec un système d’exploitation fiable, libre et gratuit. Comme son code source⁣⁣ est ouvert, cela permet aussi à la Fondation Wikimédia de le modifier pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la communauté du logiciel libre, les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia, et toujours en provenance des logiciels libres, s’ajoute encore une innovation méthodologique. Dans son article La Cathédrale et le Bazar, Eric Raymond mobilise en effet le terme « cathédrale » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « bazar », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le style de développement de Linus Torvalds, à savoir : « distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité ». À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », dans le libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut rejoindre les projets et associations Wikimédia. Ces deux observations corroborent ainsi l’existence d’un deuxième héritage en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant, a considérablement influencé l'histoire de la révolution numérique. Découvrons donc à présent, la licence libre et le mouvement de la culture libre, dont elle fut à l’origine. '''Chapitre 3 : Les licences et la culture libres.''' Dans une biographie autorisée, Christophe Masutti explique à quel point la création de la Licence publique générale GNU, en tant que première licence libre créée par Richard Stallman, représente un épisode majeur de la révolution numérique. Selon lui : La GPL apparaît comme l’un des meilleurs hacks de Stallman. Elle a créé un système de propriété collective à l’intérieur même des habituels murs du copyright. Surtout, elle a mis en lumière la possibilité de traiter de façon similaire « code » juridique et code logiciel. Le concept de distribution associé à ce nouveau type de licence fut baptisé « copyleft », par inspiration d’un jeu de mots que Richard Stallman avait trouvé dans un courrier transmis par son collègue Don Hopkins. Le principe novateur de ce concept est d'obliger toute production de code dérivé à se soumettre à la même licence libre que le code d’origine. C’est d’ailleurs la raison pour laquelle beaucoup de gens parlent de licence « virale » ou « récursive » en faisant référence à celle-ci. Quant à l'importance de cette clause, elle repose sur le fait d'interdire toute privatisation d'un code informatique produit sous licence libre. Sans celle-ci, un tel code risque en effet d'être récupéré, puis modifié, avant d’être placé sous un habituel copyright de type « tous droits réservés », dans le but de commercialiser son usage. En 2001 et dans la mouvance provoquée par l'arrivée des licences libres, une organisation internationale sans but lucratif, intitulée Creative Commons, a entamé la promotion du partage et la réutilisation de la créativité et des connaissances grâce à la fourniture d’outils juridiques gratuits. Pour ce faire, elle met régulièrement à jour une panoplie de licences inspirées par la GNU, mais spécialement adaptées aux œuvres de l'esprit, telles que les productions littéraires, musicales, photographiques et vidéo, ainsi que les bases de données. Contrairement aux licences libres fournies par la Free Software Foundation, conçues pour protéger du code informatique, les licences fournies par Creative Commons offrent la possibilité de sélectionner de nombreuses clauses pour protéger une œuvre. Avec le label CC, pour Creative Commons, on peut ainsi appliquer, ou ne pas appliquer, la clause « BY », qui oblige à créditer l'auteur, et la clause « SA », pour Share Alike, qui ordonne le partage à l’identique comme décrit précédemment. Après quoi il est encore possible d'ajouter la clause « NC », qui impose un usage non commercial, et finalement, la clause « ND », pour Non Derivative, qui exige que l’œuvre soit utilisée ou reproduite dans son intégralité et sans modification. Le mouvement Wikimédia a choisi la licence CC.BY-SA pour protéger les contenus publiés dans tous ses projets. Cela à l'exception de la banque de données Wikidata, qui au même titre que les descriptions apportées aux fichiers téléchargés dans la médiathèque Wikimedia Commons, publie ses informations structurées sous licence CC0. Cette dernière licence est ainsi ce qui se rapproche le plus du domaine public, avec cet avantage de ne pas devoir mentionner les auteurs dans le traitement et la réutilisation du contenu de la base de données. Cependant, il en résulte que les informations en question peuvent être réutilisées par des tiers qui ne sont plus obligés de citer leurs sources, comme le font les agents conversationnels des intelligences artificielles génératives, appelés couramment chatbots. Contrairement à la licence CC.BY-SA, la licence CC0 est donc moins apte à pérenniser les projets Wikimédia, puisqu'elle permet d'invisibiliser ces derniers au risque de réduire les chances d'arrivée de nouveaux contributeurs et contributrices. De plus, sans la clause SA qui assure l'application du copyleft, toutes les informations récupérées au sein de Wikidata et de Wikimedia Commons sont aussi susceptibles de quitter le monde du libre. Quoi qu’il en soit, les licences Creative Commons, inspirées par la licence libre de Richard Stallman, apparaissent finalement comme un troisième héritage en provenance du mouvement du logiciel libre et de la culture libre qui en est issue. Grâce à la licence CC BY SA, les éditeurs des projets Wikimédia bénéficient effectivement d'une certaine reconnaissance, tout en étant assurés qu'aucun copyright sur leurs travaux ne puisse exclusivement profiter à une personne ou une compagnie. De plus, la licence libre appliquée au système d'exploitation installé sur les serveurs Wikimédia, garantit elle aussi un certain respect des contributeurs, puisqu'elle permet de vérifier si le code informatique ne compromet pas leurs vies privées. Avec tous les autres apports en provenance du logiciel libre, ce sont là deux choses importantes qui ont permis l'apparition du mouvement Wikimédia. Toutefois, cela ne pouvait pas suffire à la création du projet Wikipédia qui en fut le point de départ. Car sans un espace numérique, mondial et libre d’accès, tel qu'il fut créé par Internet et l'espace web, aucune encyclopédie collaborative de cette envergure n'aurait pu voir le jour. '''Chapitre 4 : Le réseau Internet.''' L’histoire du réseau Internet constitue un nouvel épisode captivant de la révolution numérique, sans lequel le mouvement Wikimédia n’aurait jamais pu émerger. D’un point de vue purement technique, ce réseau informatique a été mis au point dans les années 1970, avant l’adoption généralisée du protocole TCP/IP, toujours en usage à ce jour. Ce dernier fut inventé par Vint Cerf et Robert Elliot Kahn, quand ils travaillaient pour la Defense Advanced Research Projects Agency, rattachée au département de la Défense américaine. L'une des premières présentations de leur projet fut ainsi réalisée lors d’une conférence organisée par l’International Network Working Group, une instance créée pour assurer la gouvernance mondiale du réseau informatique. Sur base de ces informations, on peut penser qu’Internet a été créé par des militaires. Cependant, Une contre-histoire de l’Internet, nous révèle que les créateurs et les premiers utilisateurs d’ARPANET, considéré comme l’ancêtre d’Internet, étaient davantage des étudiants hippies et amateurs de LSD, que des militaires bien drillés. D’ailleurs, avant la standardisation du protocole TCP/IP, ARPANET fonctionnait depuis plus d’un an avec un autre protocole de transition intitulé Network Control Program. Or, celui-ci avait été mis au point, en février 1969, par le Network Working Group, un groupe informel d’étudiants rassemblés autour de Steve Crocker, lorsqu’il ne détenait encore qu’une simple licence, au niveau de sa formation universitaire. Bien que rarement cité dans l’histoire d’Internet, ce groupe a pourtant mis en place la procédure RFC, pour Request For Comments, reconnue comme « l’un des symboles forts de la "culture technique" de l’Internet, marquée par l’égalitarisme, l’autogestion et la recherche collective de l’efficience ». Soit trois principes et une procédure, qui aujourd’hui encore sont appliqués sur le site Méta-Wiki, dans lequel s'organise la gestion communautaire du mouvement Wikimédia. Cela alors qu'au sein des autres projets dédiés à la production de contenus pédagogiques, des processus similaires de recherche de consensus ont fait leur apparition. Il faut ensuite savoir que les liens entre ARPANET et l’armée ont disparu avec l’apparition du MILNET, un réseau entièrement dédié aux activités militaires, rebaptisé NIPRNet, pour Non-classified Internet Protocol Router Network, en 1990. Après une séparation définitive en 1983, précisément l’année où Richard Stallman postait sa demande d’aide pour le projet GNU, le réseau ARPANET resta uniquement dédié à la recherche et au développement. À cette époque, le réseau ne comprenait pas plus de 600 machines connectées, ce qui n'a donc rien de comparable avec ce vaste réseau informatique mondial que nous connaissons aujourd’hui et qui fut fortement développé au cours des années 1990. Pour en assurer l’entretien technique, une organisation non gouvernementale, a été créée en 1992, sous l'appellation d’Internet Society. Celle-ci devait aussi veiller au respect des valeurs fondamentales liées au bon fonctionnement du réseau. Car avant d'atteindre des milliards d’appareils connectés, il a d’abord fallu réglementer les nombreuses dorsales internet intercontinentales, sans lesquelles la transmission du protocole TCP/IP partout dans le monde n'aurait pas été possible. Quant à l’état d’esprit des créateurs d'Internet, un article intitulé Quarante ans après, mais qui donc créa l’internet ? apporte un éclairage particulièrement intéressant au sujet des liens que l'on peut établir entre le mouvement Wikimédia et l'histoire d'Internet. Dans son témoignage, Michel Elie, cet ingénieur en informatique, membre du Network Working Group cité précédemment, et responsable de l’Observatoire des Usages de l’Internet, nous explique en effet ceci. Le succès de l’internet, nous le devons aux bons choix initiaux et à la dynamique qui en est résultée : la collaboration de dizaines de milliers d’étudiants, ou de bénévoles apportant leur expertise, tels par exemple ces centaines de personnes qui enrichissent continuellement des encyclopédies en ligne telles que Wikipédia. Au courant des années 1990, le milieu informatique universitaire semblait donc toujours fortement imprégné des idéaux de la contre-culture des années 1960, produit par les baby boomers dans le contexte de la guerre du Vietnam. Afin d'illustrer les idées véhiculées à cette époque, voici un paragraphe extrait d'un ouvrage publié en 1970, et intitulé Vers une contre-culture : Réflexions sur la société technocratique et l’opposition de la jeunesse. Dans celui-ci, Théodore Roszak explique ceci. Le projet essentiel de notre contre-culture : proclamer un nouveau ciel et une nouvelle terre, si vastes, si merveilleux que les prétentions démesurées de la technique soient réduites à n’occuper dans la vie humaine qu’une place inférieure et marginale. Créer et répandre une telle conception de la vie n’implique rien de moins que l’acceptation de nous ouvrir à l’imagination visionnaire. Nous devons être prêts à soutenir ce qu’affirment des personnes telles que Blake, à savoir que certains yeux ne voient pas le monde comme le voient le regard banal ou l’œil scientifique, mais le voient transformé, dans une lumière éclatante et, ce faisant, le voient tel qu’il est vraiment. À la suite de cette lecture, il peut sembler paradoxal qu’une contre-culture, qui voit la technique comme inférieure et assimile la science au banal, puisse avoir un lien avec le milieu scientifique universitaire qui fut à l'origine d'Internet. Cependant, une réponse à cette énigme fut apportée par Fred Turner, par la publication de son livre intitulé : « Aux sources de l’utopie numérique : De la contre-culture à la cyberculture ». Grâce à cet ouvrage, on découvre que le mouvement hippie utilisera tout ce qui était à sa disposition à l’époque pour parvenir à ses fins : LSD, spiritualités alternatives, mais également, objets technologiques les plus en pointe. Cela grâce notamment à l’influence de Steward Brand, le créateur d'un catalogue interactif, qui peut être considéré comme l'ancêtre analogique des groupes de discussions numériques apparus des années plus tard. Comme autre indication, il y a ensuite les propos tenus en 1992, lors d’une plénière de la 24ᵉ réunion du groupe de travail sur l’ingénierie Internet, par David D. Clark, un autre pionnier d’Internet. Durant cette rencontre, ce chef de projet prononça des paroles restées dans les annales. « Nous récusons rois, présidents et votes. Nous croyons au consensus et aux programmes qui tournent ». Deux phrases seulement, mais qui, dans le cadre du milieu informatique, permettent de croire que le mépris de la contre-culture envers la technique et la science, s'est transformé en un refus d’autorité et une recherche de consensus. Quoi qu'il en soit, le développement d'Internet ne s'est pas fait sans conflits idéologiques importants. On peut d'ailleurs se demander aujourd'hui à quoi ressemblerait Internet, s'il n'avait jamais été commercialisé. Cela s'est passé en novembre 1994, lorsque l’association sans but lucratif Advanced Network and Services, chargée de gérer les accès à Internet, a fait le choix de vendre ses activités. Cette décision faisait suite à un appel à des fonds privés pour financer d'importants changements dans l'infrastructure du réseau. Profitant de l'occasion, la société commerciale America Online a ainsi repris à son compte la gestion des connexions à Internet, après avoir effectué un versement de 35 millions de dollars. Trente ans plus tard, Internet est devenu ce réseau que nous expérimentons aujourd'hui, à savoir : un réseau dominé par des sociétés privées les plus riches au monde. Dans ce contexte et parmi les 100 sites web les plus visités, seul le nom de domaine Wikipédia appartient à une organisation non lucrative. Cela explique donc pourquoi le mouvement Wikimédia, via son encyclopédie et la fondation qui l'héberge, représente à ce jour, et dans l'espace web, l'expression la plus visible de la philosophie des pionniers d’Internet. Plus qu'un héritage, cette situation peut être vue comme une mission perpétuée au sein d'un espace envahi par une culture marchande et capitaliste. Il s'agit là d'une information importante qu'il faut retenir au sujet du mouvement Wikimédia. Elle ne clôture pas pour autant tout ce qu'il faut savoir au sujet des évènements qui ont permis la création d’une encyclopédie mondiale, libre et collaborative. Car avant cela, il nous reste encore à découvrir l'histoire du World Wide Web, un espace numérique sans lequel la création de Wikipédia n'aurait jamais été possible. '''Chapitre 5 : Le World Wide Web.''' Maintenant que le lien entre la création d'Internet et le mouvement Wikimédia est établi, découvrons à présent l'application la plus connue du réseau, que l'on nomme le World Wide Web, ou plus simplement « Web ». C'est Tim Berners-Lee qui en fut l’inventeur, lorsqu’il était encore actif à l'Organisation européenne pour la recherche nucléaire. Il avait pour idée de créer un espace d’échange public par l’intermédiaire d'Internet, et pour y parvenir, il mit au point le logiciel « WorldWideWeb », ensuite rebaptisé Nexus, pour éviter toute confusion entre les deux termes. Grâce à un système d’indexation appelé hypertexte, ce programme informatique a permis de produire et de connecter des espaces numériques, que l'on intitule sites Web. Ceux-ci sont composés de pages web, hébergées sur des ordinateurs distants, mais connectés entre eux au travers du réseau Internet. Pour permettre ce type de connexion, Berners-Lee mit au point le Hypertext Transfer Protocol ou HTTP, un nouveau protocole de communication simple en soi, mais dont la mise en œuvre technique est compliquée. Pour veiller au bon fonctionnement et au bon usage de l'espace web, des règles de standardisation ont tout d'abord été édictées par l’association Internet Society. Après quoi, Berners-Lee fonda le W3C, un consortium international dont la devise est : « un seul Web partout et pour tous ». Si ce slogan nous apparaît très naturel aujourd’hui, il faut toutefois savoir que l'espace Web a bien failli être régi séparément par des acteurs commerciaux, avec tous les droits d'accès que cela aurait pu engendrer. À partir du trente avril 1993, jour du dépôt du logiciel Nexus dans le domaine public par Robert Cailliau, un collègue de Berners-Lee chargé de la promotion de son projet, un tel scénario était en effet possible. Sauf qu'après le départ de Berners-Lee, devenu président du W3C, François Flückiger, qui avait repris son poste au sein du CERN, eut la présence d'esprit de réagir à temps. Selon le livre Alexandria qui parcourt l'histoire de Robert Caillau, voici ce qui aurait pu se passer si le code de l’éditeur HTML n'avait finalement pas été placé sous licence libre. La philanthropie de Robert, c’est très sympa, mais ça expose le Web à d’horribles dangers. Une entreprise pourrait s’emparer du code source, corriger un minuscule bug, s’approprier le « nouveau » logiciel et enfin faire payer une licence à ses utilisateurs. L’ogre Microsoft, par exemple, serait du genre à flairer le bon plan pour écraser son ennemi Macintosh. Les détenteurs d’un PC devraient alors débourser un certain montant pour profiter des fonctionnalités du Web copyright Microsoft. Les détenteurs d’un Macintosh, eux, navigueraient sur un Web de plus en plus éloigné de celui vendu par Bill Gates, d’abord gratuit peut-être, avant d’être soumis lui aussi à une licence. Face à un tel scénario, nous découvrons de nouveau à quel point le concept de licence libre a fondamentalement changé le cours de la révolution numérique. Sans cela, nos expériences et nos usages de l'espace numérique auraient été totalement différents. L'utopie Wikipédia, par exemple, n'aurait certainement pas vu le jour, en raison de l'éclatement des espaces numériques et des coûts d'accès auxquels seraient confrontés les bénévoles qui ont construit le projet. Quoi qu'il en soit, et au niveau technique, une fois l'espace web apparu, il ne manquait plus que l'apparition des plateformes Wiki pour permettre la création d'une encyclopédie collaborative au format numérique. '''Chapitre 6 : Les plateformes Wiki.''' Un wiki, ou un moteur de wiki, est un logiciel que l'on installe sur un serveur informatique pour permettre la création d’un site web éditable et configurable à l’aide d’un simple navigateur. Plus précisément, c’est un système de gestion de contenu, dans lequel le code HTML, CSS, JavaScript et Lua, ainsi que certains paramètres, peuvent être modifiés par tous les internautes. Cela peut se faire en se connectant à un compte utilisateur, afin de bénéficier des droits de modification et d’administration qui lui sont accordés, ou en utilisant la configuration attribuée par défaut aux personnes non connectées. Sur les pages web d'un wiki, chaque modification provoque un nouvel enregistrement complet du code source qui la compose. De la sorte, il est toujours possible, à partir d’une page reprenant l’historique des modifications, de rétablir l'une de ses anciennes versions. Grâce à ce système, on peut ainsi savoir quelle personne, ou quelle adresse IP est à l’origine d’un changement, et même voir l’endroit où la modification a été faite, et à quel moment celle-ci a été réalisée. Le premier logiciel Wiki, qui portait le nom de WikiWikiWeb, a été créé et placé sous licence libre GPL par Ward Cunningham en mars 1995. Grâce à la licence, d’autres programmes wiki ont vu le jour en copiant ou s’inspirant du code source de WikiWikiWeb, ou des autres projets wiki qui l'avaient fait auparavant. Cette émulation récursive, qui donna naissance à toute une panoplie de projets wiki, est donc à nouveau une belle illustration des retombées positives que peut susciter l'application d'une licence libre. Parmi les différents logiciels Wiki disponibles, UseModWiki fut choisi par la société Bomis qui finança la création du premier projet Wikipédia en anglais. C'était un choix judicieux, car l’éclatement de la bulle spéculative d’Internet, à la fin des années 2000, confrontait l'entreprise à de grosses difficultés financières. Un programme gratuit, simple d’utilisation et peu gourmand en ressources informatiques, convenait donc parfaitement dans ce cadre. UseModWiki fut par après remplacé par un autre moteur de Wiki sans nom, mais plus performant et toujours produit sous licence libre. Ce dernier fut ensuite amélioré par plusieurs programmeurs, dont Brion Vibber, le premier employé de la Fondation Wikimédia, avant d’être finalement intitulé MediaWiki. Avec l’aide de nouveaux employés et des bénévoles actifs sur le site mediawiki.org, ce système de gestion de contenu finit par apparaitre en tête du classement des wikis les plus utilisés. Toujours grâce à sa licence libre, des milliers d'autres personnes et projets ont effectivement pu développer des sites Web, sans nécessairement faire partie du mouvement Wikimédia. Ce succès a par ailleurs justifié la programmation de rassemblements annuels entre 2016 et 2020, entre personnes et organismes qui utilisent le programme, pour discuter de son développement et de ses usages. Ceci étant dit, il existe dans la liste des Wikis d’autres logiciels libres intéressants, tels que DokuWiki, rendu populaire par sa simplicité d’installation et d’usage. Jusqu’à ce jour cependant, seul MediaWiki semble suffisamment stable et puissant pour permettre le développement optimal de l’ensemble des projets Wikimédia. Avec parmi ceux-ci, bien sûr, Wikipédia, l'encyclopédie libre et universelle, dont nous allons enfin découvrir la mise en place dans ce prochain chapitre. '''Chapitre 7 : L’encyclopédie libre et universelle.''' Dans les chapitres précédents, nous avons découvert toutes les innovations techniques et culturelles, sans lesquelles Wikipédia n’aurait jamais pu devenir la plus grande encyclopédie libre et universelle connue au monde. Son objectif est de synthétiser la totalité du savoir humain, ce qui n’est autre finalement, qu’un vieux rêve de notre humanité. Trois cents ans avant Jésus-Christ et durant la création de la bibliothèque d’Alexandrie, ce désir était aussi celui de Ptolémée Iᵉʳ. Puis, deux siècles plus tard, Denis Diderot publie, avec Jean Le Rond d'Alembert et Louis de Jaucourt en 1751, la première édition de l’Encyclopédie ou Dictionnaire raisonné des sciences, des arts et des métiers. Quant à Paul Otlet, qui a créé avec Henri La Fontaine la classification décimale universelle en usage depuis 1905, il s’était mis en tête de répertorier l’ensemble du savoir humain au sein d'un seul édifice. Peu connu à ce jour, ce documentaliste belge rêvait pourtant de cataloguer le monde et de rassembler toutes les connaissances humaines, sous la forme d’un gigantesque répertoire bibliographique universel, situé à l'intérieur d'un Mundaneum. En 1934, dans le Traité de documentation écrit par celui qui voulait « classer le monde », Otlet décrit, de manière particulièrement visionnaire, un possible partage du savoir et de l’information. Ici, la Table de Travail n’est plus chargée d’aucun livre. À leur place se dresse un écran et à portée un téléphone. Là-bas, au loin, dans un édifice immense, sont tous les livres et tous les renseignements, avec tout l’espace que requiert leur enregistrement et leur manutention… De là, on fait apparaître sur l’écran la page à lire pour connaître la question posée par téléphone avec ou sans fil. Un écran serait double, quadruple ou décuple s’il s’agissait de multiplier les textes et les documents à confronter simultanément ; il y aurait un haut-parleur si la vue devrait être aidée par une audition. Une telle hypothèse, un Wells certes l’aimerait. Utopie aujourd’hui parce qu’elle n’existe encore nulle part, mais elle pourrait devenir la réalité de demain pourvu que se perfectionnent encore nos méthodes et notre instrumentation. Avant l'apparition des intelligences artificielles et à peu de choses près, cette utopie décrite en 1934 par Otlet correspond à l'usage que l'on fait du réseau Internet et de son espace web, lorsqu'on recherche de l'information. Premièrement, allumer un système informatique, avec ou sans fil et muni d'un écran, ensuite, poser une question dans un moteur de recherche, puis finalement, être redirigé, comme cela arrive très souvent, vers l'une des versions linguistiques de Wikipédia. Ce scénario, dans lequel les moteurs de recherche jouent un rôle central, explique la popularité de l'encyclopédie libre. D'autres projets similaires étaient pourtant apparus sur le Web avant l'arrivée de Wikipédia. Environ trois ans avant sa création, Aaron Swartz, un activiste de la culture libre qui avait juste douze ans à l'époque, avait par exemple lancé une sorte de site encyclopédique produit et régi par ses usagers. Appelé The Info Network, ce site web avait d'ailleurs permis à son auteur de recevoir l'ArsDigita Prize, un prix décerné aux jeunes créateurs de projets « utiles, éducatifs, collaboratifs et non commerciaux ». Il faut savoir ensuite que l'expression « encyclopédie libre et universelle » apparut pour la première fois sur le Net sous la plume de Richard Stallman et durant l'année 2000, soit approximativement un an avant la naissance de Wikipédia. C'était dans un essai intitulé The Free Universal Encyclopedia and Learning Resource, qui, selon son auteur, avait été rédigé deux ans avant sa publication sur la liste de diffusion du projet GNU. Repris ci-dessous, un extrait de ce texte, présente les particularités du projet. Le World Wide Web a le potentiel de devenir une encyclopédie universelle couvrant tous les domaines de la connaissance et une bibliothèque complète de cours d’enseignement. Ce résultat pourrait être atteint sans effort particulier, si personne n’intervient. Mais les entreprises se mobilisent aujourd’hui pour orienter l’avenir vers une voie différente, dans laquelle elles contrôlent et limitent l’accès au matériel pédagogique, afin de soutirer de l’argent aux personnes qui veulent apprendre. Nous ne pouvons pas empêcher les entreprises de restreindre l’information qu’elles mettent à disposition ; ce que nous pouvons faire, c’est proposer une alternative. Nous devons lancer un mouvement pour développer une encyclopédie libre universelle, tout comme le mouvement des logiciels libres nous a donné le système d’exploitation libre GNU/Linux. L’encyclopédie libre fournira une alternative aux encyclopédies restreintes que les entreprises de médias rédigeront. En parlant d'un « mouvement pour développer une encyclopédie libre universelle », Stallman anticipait donc, sans le savoir, l'arrivée du mouvement Wikimédia, qui ne se concrétisa que des années plus tard. Quant à la soixantaine de paragraphes qui décrivent son projet, on y retrouve, dans une forme presque identique, les cinq principes fondateurs, qui ont guidé la création de Wikipédia et qui sont toujours actifs à ce jour. Le premier consiste bien sûr à créer une encyclopédie ; le deuxième réclame une neutralité de point de vue, chose que Stallman expliquait déjà en écrivant qu’en cas de controverse, plusieurs points de vue seront représentés, le troisième implique le respect des droits d’auteur et l’adoption d'une licence libre, celle précisément dont Stallman avait été l'initiateur, le quatrième inscrit le projet dans une démarche collaborative, là ou Stallman précisait déjà que « tout le monde est le bienvenu pour écrire des articles », et le cinquième enfin, stipule qu’il n’y a pas d’autres règles fixes, une position somme toute très courante dans le milieu des hackers dont Stallman faisait partie. Contrairement à ce que l'on peut croire, le projet d'encyclopédie libre et universelle n'était donc pas une idée originale de Jimmy Wales et Larry Sanger, tous deux reconnus à ce jour comme les deux fondateurs de Wikipédia. Ce qu'ils firent en revanche, c'est d'exploiter l'idée au sein de la société Bomis, détenue par Jimmy Wales, pour enrichir son encyclopédie commerciale Nupedia. Cette dernière avait été lancée en avril 2000, soit environ dix mois avant Wikipédia, et sa rédaction était assurée par des experts engagés au sein d’un processus éditorial strict et formel. Malheureusement pour la firme Bomis, le nombre d’articles ne progressait que très lentement. Dans le but d'accélérer le processus, Larry Sanger, docteur en philosophie et employé par Bomis pour assurer le rôle de rédacteur en chef de Nupedia, eut l'idée d'installer un logiciel wiki sur les serveurs de son entreprise. L'objectif était d'ouvrir un site web participatif, dans lequel des volontaires pourraient créer des articles encyclopédiques, pour qu'ils soient ensuite intégrés dans le projet commercial. Malgré le manque d’enthousiasme de son employeur, Sanger mit ses idées en application, et c'est donc ainsi que débuta l’histoire de Wikipédia, avec sa toute première version en anglais. C’était le 15 janvier 2001, précisément le même mois où Richard Stallman mit en ligne son propre projet d’encyclopédie libre et universelle, qu'il souhaitait intituler GNUPedia. Étonnamment, les noms de domaine gnupedia .com .net et .org avaient déjà été enregistrés au nom de Jimmy Wales, ce qui obligea Stallman à rebaptiser son projet GNE. Ce fait est d'autant plus surprenant que Wales affirma des années plus tard : « n’avoir eu aucune connaissance directe de l’essai de Stallman lorsqu’il s’est lancé dans son projet d’encyclopédie ». Le site GNE ne ressemblait cependant pas vraiment à une encyclopédie, mais plutôt à un blog collectif ou une base de connaissance, tandis que sa page d’accueil précisait clairement qu’il s’agissait d’une bibliothèque d’opinion. Quant à sa modération, elle avait demandé d'engager un employé, car elle s'est avérée bien plus compliquée que prévu. À côté de cela, et probablement grâce aux spécificités de l’environnement wiki et aux soutiens apportés par Jimmy Wales et Larry Sanger, Wikipédia réussit à mettre en place une organisation efficace au sein d'une communauté d'éditeurs grandissante. Peut-être en raison de la concurrence libre faite par le projet GNE, Jimmy Wales décida d'abandonner le copyright que Bomis détenait sur son encyclopédie commerciale Nupedia, pour le remplacer par une licence Nupedia Open Content. Peu de temps après, il décida finalement d'adopter la licence de documentation libre GNU conçue pour protéger les textes de documentation des logiciels libres. Ce dernier choix fut une stratégie efficace, puisque cela incita Richard Stallman à transférer tout le contenu de son projet GNE vers Nupedia, et à encourager tout le monde à contribuer sur Wikipédia. Parmi les autres actions de Jimmy Wales qui ont contribué au succès de Wikipédia, il y eut cette idée d'ouvrir le projet aux « gens ordinaires ». C’était un choix qui s’opposait aux idéaux de Larry Sanger, qui de loin préférait le modèle de Nupedia avec son système de relecture par des experts. Cependant, Jimmy Wales, en tant qu'homme d’affaires, visait une croissance plus rapide du contenu de l'encyclopédie. Cette croissance ne s'est toutefois pas faite sans difficulté. Le 26 février 2002, en effet, l'Enciclopedia Libre Universal en Español, un projet dissident du projet Wikipédia, fit son apparition. C'était une réaction à de la censure, à l'existence d'une ligne éditoriale et à la possibilité de voir apparaitre des publicités dans Wikipédia. En raison des remises en question que cette séparation suscitait parmi les bénévoles actifs dans les projets, Jimmy Wales renonça finalement à l'usage de la publicité et mit de côté ses visions en matière de profit. Il faut aussi tenir compte du fait que cet évènement est survenu lors de l'éclatement de la bulle spéculative Internet et du krach boursier de 2001-2002. Une conjoncture qui plaçait la société Bomis dans des difficultés financières, et surtout, dans l'incapacité de payer le salaire de Larry Sanger, son seul employé. En mars 2002 et après un mois d’activité bénévole, l’ex-employé décida alors de quitter les fonctions, qu'il occupait depuis un peu plus d'un an, dans Nupedia et Wikipédia. Avec le seul soutien de Jimmy Wales, les deux encyclopédies purent toutefois poursuivre leurs développements, toujours avec le concours d'experts dans Nupedia et d'une communauté bénévole au niveau de Wikipédia. Néanmoins, en septembre 2003 et vu l'écart qui se creusait entre les deux projets, l'encyclopédie Nupedia fut fermée et ses quelques dizaines d'articles transférées vers les milliers d'autres que comprenait déjà le projet Wikipédia. Trois ans plus tard, Larry Sanger n’avait pas dit son dernier mot. En septembre 2006, il décida en effet de lancer sur fonds propres une encyclopédie intitulée Citizendium. Cette plateforme écrite en anglais uniquement et toujours active à ce jour, repose sur un système d’expertise, dans lequel les contributrices et les contributeurs doivent déclarer leur identité réelle. En avril 2026 cependant, Citizendium reprenait moins de 2000 articles, tout avancement confondu, tandis que le projet Wikipédia en anglais en regroupait déjà plus de 7 millions. Voici donc comment est née la plus grande encyclopédie du monde, dont la taille et la visibilité n'avaient jamais été égalées auparavant. Une encyclopédie qui, de plus, s'est rapidement déclinée en de nombreuses versions linguistiques, à l'instar de sa version francophone, lancée moins de quatre mois après le projet original en anglais. Toutes ces versions ont formé les premières bases d’une organisation mondiale, bientôt chapeautée par une fondation. Avant cela, d'autres projets pédagogiques et collaboratifs ont vu le jour au côté de Wikipédia. Intitulés projets frères, ceux-ci se constituent à leur tour en de nombreuses versions linguistiques, tout en poursuivant le processus de création du mouvement Wikimédia. '''Chapitre 8 : L'arrivée des projets frères.''' Dans le but de développer des contenus pédagogiques qui ne trouvaient pas leur place dans Wikipédia, d’autres projets pédagogiques et collaboratifs ont vu le jour, pour former ce que l'on appelle couramment aujourd'hui : l’écosystème Wikimedia. La naissance de tous ces projets, ainsi que les évènements importants qui ont contribué au développement du mouvement, ont été repris dans une ligne du temps réalisée par Guillaume Paumier, à l’occasion du dixième anniversaire de Wikipédia. Grâce à ce graphique, qui complète avantageusement la page Wikimedia News, on peut découvrir en détail l'évolution des projets, des versions linguistiques, du nombre de contributeurs et d'articles, tout en observant le développement du mouvement dans son ensemble. Parmi tous les projets frères, le premier à apparaître fut Méta-Wiki, une plateforme de référence pour centraliser la gestion de l'ensemble des sites web hébergés par la fondation Wikimédia. Dans un premier temps, cet espace communautaire en ligne a répondu à la nécessité de traiter en un seul lieu les questions communes aux différentes versions linguistiques de Wikipédia. Aujourd'hui, le site web est le principal endroit de coordination et de gestion de l'ensemble du mouvement Wikimédia. On y retrouve énormément d'informations au sujet des projets en ligne, et peut-être plus encore, concernant la Fondation et les organismes affiliés. Après Méta-Wiki, sept autres projets de partage de la connaissance ont fait leur apparition, avant d'être déclinés à leur tour en plusieurs versions linguistiques. Tous ces projets émergent en général sur l’initiative d’un petit groupe de personnes actives au sein d’un projet préexistant. Ce fut le cas du projet Wiktionnaire en anglais, le deuxième projet à voir le jour après Méta-Wiki, en décembre 2002, soit deux ans avant la version francophone apparue en mars 2004. Il est intéressant d'observer que la version francophone du Wiktionnaire n’a pas été créée à partir du projet anglophone, mais bien depuis le projet Wikipédia en français. D'ailleurs, on peut retrouver dans les archives de ce dernier projet, un débat concernant la pertinence de cette création, dont voici un extrait. En fait, ce qui me peine vraiment avec le projet Wiktionary, c’est que, alors qu’on essaie de rassembler les gens (pas facile) pour créer une sorte de tour de Babel de la connaissance, tâche bien longue et difficile, ce nouveau projet va disperser les énergies pour une raison qui ne me semble pas valable. C’est la création de Wiktionary qui va créer des redondances. À mon avis il existera rapidement des pages sur le même mot, mais ne contenant pas les mêmes informations. Pour quelle raison ces connaissances devraient-elles être séparées ? Les encyclopédies sur papier devaient faire des choix à cause du manque de place, mais nous, pourquoi le ferions-nous ??? "Wikipédia n’est pas un dictionnaire" n’est pas un argument à mon avis... si vraiment c’était pas un dictionnaire, il faudrait virer tout un tas d’article. Je ne comprends vraiment pas cette volonté de séparer la connaissance entre ce qui est encyclopédique et ce qui n’est qu’une définition. Réponse Pour moi, ce qu’est Wiktionary, c’est une partie de Wikipédia s’intéressant plus particulièrement aux aspects linguistiques des mots. La différence que je verrais entre la partie dictionnaire de Wikipédia et sa partie dite encyclopédique, c’est que la partie dictionnaire s’intéresserait au sens des mots eux-mêmes alors que la partie encyclopédie s’attache plus à faire ressortir un état des connaissances à un moment donné. Le pourquoi de la séparation avec la partie encyclopédie tient plus à des raisons techniques qu’à une volonté de monter un projet indépendant. En effet, et à mon humble avis, un dictionnaire nécessite une plus grande rigueur (de présentation) qu’une encyclopédie. Ceci entraîne beaucoup de problème et entre autres le choix de la mise en forme des articles du dictionnaire. Créer un nouveau projet, c’est effectivement créer de nouveaux sites web, qui devront faire l’objet d’une nouvelle gestion, tant pour les serveurs de la Fondation, que pour la nouvelle communauté de contributeurs. L’importation de pages d’un projet à l'autre ou la traduction de celles-ci sont bien sûr toujours possibles, mais cela duplique alors aussi la maintenance et les mises à jour. Le choix de scinder un projet, en faveur d’une plus grande liberté, comporte donc certains coûts humains et financiers. Ce prix à payer n'a pour autant pas empêché le projet anglophone Wikibooks de faire son apparition le 10 juin 2003, soit près d’un an avant Wikilivres, la version francophone du projet, apparue le 22 juillet 2004. Cette dernière création avait de nouveau été débattue au sein de la communauté Wikipédia en français, et non pas dans le Wikibooks en anglais. Quant à l'objectif commun aux deux projets linguistiques, il était de créer une « bibliothèque de livres pédagogiques libres que chacun peut améliorer ». Environ un an après la création du projet en anglais, un nouvel espace de noms intitulé Wikijunior fut mis en place au sein de la bibliothèque en ligne. Ce sous-projet avait été créé pour répondre à un financement de la fondation Beck, qui cherchait à promouvoir la production de nouvelles littératures pour des enfants de huit à onze ans. Peu de temps après, cette tranche d’âge fut toutefois élargie de zéro à douze ans au niveau du projet francophone, quand le sous-projet y fut adopté. Ces deux évènements témoignent ainsi qu'il est toujours possible qu'un sous-projet apparaisse dans un projet Wikimédia. Comme autre exemple, il y a aussi le WikiJournal, un sous-projet développé au sein du projet Wikiversité en anglais et qui reçut le prix de l’Open Publishing Awards en 2019. Une demande fut faite pour qu'il puisse bénéficier d'un nouveau site web dans le but de pouvoir se développer en dehors de Wikiversité. Malheureusement pour les initiateurs, la demande est restée sans suite jusqu'à ce jour, après que le conseil d’administration de la Fondation, chargé de répondre à celle-ci, considéra que le projet n’était pas suffisamment abouti. Il faut savoir qu'avant cela, le projet Wikiversité, dans lequel est né Wikijournal, avait lui-même été un sous-projet du projet Wikibooks. Initialement, il visait à « créer une communauté de personnes qui se soutiennent mutuellement dans leurs efforts éducatifs ». Cependant, en août 2005, une longue discussion remit en question l’existence du sous-projet Wikiversité dans Wikibooks. Au terme de celle-ci, la décision fut prise de transférer Wikiversité et son contenu sur le site Méta-Wiki, là où de nouvelles discussions ont abouti à l’idée de faire de Wikiversité un nouveau projet indépendant. Déjà à l'époque, le conseil d’administration de la Fondation Wikimédia se montrait réticent à l'ouverture de nouveaux projets, et sa réaction fut de demander l'ouverture d'un sondage au sein de la communauté. Celui-ci devait rassembler une majorité des deux tiers en faveur de l'ouverture du nouveau site web. Un résultat qui fut finalement obtenu, mais pas sans de longs débats, dont voici un extrait. La principale raison pour laquelle la Fondation Wikimédia ne veut pas lâcher le morceau est une simple question de bureaucratie et la crainte que le projet ne devienne une autre Wikispecies. Wikispecies est une idée cool, mais les fondateurs du projet se sont dégonflés à mi-chemin de la mise en place du contenu et ont décidé de faire une révision majeure qui a pris plus de temps que ce que tout le monde était prêt à mettre. Le même problème s’applique à Wikiversity en ce qui concerne la Fondation, parce que les objectifs et les buts de ce projet ne sont pas clairement définis, et il semble que les participants essaient de mordre plus qu’ils ne peuvent mâcher en proposant une université de recherche multicollèges entière, avec un statut de recherche et une accréditation, à former de toutes pièces plutôt qu’un simple centre d’éducation pour adultes avec quelques classes. En novembre 2005 et malgré les résultats positifs du sondage, l'indépendance du projet Wikiversité ne fut toutefois pas acceptée par cinq membres du conseil. Ceux-ci réclamaient une réécriture de la proposition pour en exclure la remise de titre de compétence, la conduite de cours en ligne, et de clarifier le concept de plate-forme e-learning. Quand ces rectifications furent faites, le projet bénéficia d'une période d’essai de plusieurs mois, jusqu'à ce que les amendements apportés au projet de départ soient enfin acceptés, le 31 juillet 2006. Ce long temps d'attente était justifié par la création du special projects committee, qui, jusqu'en décembre 2021, fut chargé de soulager le conseil d’administration de la fondation, par rapport aux demandes de création de nouveaux projets Wikimédia. Un nouveau site, nommé Beta-Wikiversity, fut ainsi créé pour assister le lancement des différentes versions linguistiques de Wikiversité. Durant six mois, son premier objectif a d'abord été l'élaboration des lignes directrices concernant la potentialité de produire des recherches collaboratives au sein du projet. Par la suite, chaque nouvelle version linguistique, développée dans le projet Beta, devait avoir plus de 10 modifications par mois, réalisées par au moins trois personnes distinctes, avant de pouvoir bénéficier de son propre site web. À l'image de Beta-Wikiversity, le projet Wikisource possède lui aussi un site indépendant pour lancer ces nouvelles déclinaisons linguistiques. Tandis que pour tous les autres projets pédagogiques, ce lancement s'effectue sur la plateforme Wikimedia Incubator, créée à la même époque que Beta-Wikiversity. Ces trois plateformes de lancement ne concernent pas les nouveaux projets, qui doivent faire l'objet d'une acceptation par le conseil d'administration de la Fondation Wikimédia, et qui peuvent avoir pour origines des processus de création divers. Le projet Wikivoyage, par exemple, fut initialement créé en 2003 dans un Wiki extérieur au mouvement Wikimédia, là où il portait le nom de Wikitravel. Comme cela arrive parfois, ce projet sans but lucratif fut acheté par une entreprise commerciale en 2006. Mais en raison du changement de gouvernance et de l'apparition de publicités, une scission est apparue au sein de la communauté d’éditeurs. Les personnes désireuses de quitter Wikitravel lancèrent alors un nouveau site appelé Wikivoyage, qui reçut en 2007, le Webby Award du meilleur guide de voyage Internet. L'intégration de Wikivoyage dans l'écosystème Wikimédia n'a cependant été faite qu'en 2012, à la suite d'un appel à commentaires durant lequel 540 personnes sur 699 furent en faveur de l’intégration du projet. Comme cette nouvelle déclencha une migration importante depuis Wikitravel vers Wikivoyage, une plainte fut déposée par la société commerciale en charge du premier projet. Celle-ci fut toutefois rejetée et le projet Wikivoyage continua à prendre de l’ampleur au sein du mouvement Wikimédia, avec la création de nouvelles versions linguistiques. Le cas de Wikivoyage apparait toutefois comme une exception, car en général les nouveaux projets émergent des centaines de candidatures déposées sur le projet Méta-Wiki. Celles-ci se soldent bien souvent par un refus, comme ce fut le cas pour le projet WikiLang dont le but était de lancer un laboratoire linguistique. Quelques rares projets ont pourtant eu la chance d'être élus. Ce fut notamment le cas en octobre 2012, avec le lancement de la base de données structurée et sémantique Wikidata et de ses extensions Wikibase, ou plus récemment, en 2020, avec l'arrivée du projet Abstract Wikipedia et Wikifunctions. Sans vouloir entrer dans les détails, il est intéressant de savoir que l'interconnexion entre ces trois projets permet de traduire automatiquement des articles encyclopédiques dans tous les langages naturels pris en charge par le mouvement Wikimédia. Plus précisément, les phrases des articles publiées sur Abstract Wikipédia, sont formulées par des fonctions informatiques produites dans le projet Wikifunctions, dans le but de traiter les informations de la base de données sémantique Wikidata. Autrement dit, un article dans Abstract Wikipédia ne possède qu'une seule page pour toutes les langues, pareillement aux pages d'entités de Wikidata, qui ont pour titre une lettre suivie d'un chiffre. À la suite de ces explications, on observe donc que ce n'est pas la complexité qui détermine le refus projet, mais plutôt une série de critères comparables à ceux retenus pour supprimer des projets ou versions linguistiques déjà existants. À ce propos, il existe sur Méta-Wiki une liste mise à jour des différents sites hébergés par la Fondation Wikimédia, dont l'existence est remise en cause. Dans celle-ci, on retrouve essentiellement des versions linguistiques de projets, qui n’ont pas réussi à poursuivre leurs développements, bien qu'un projet entier soit toujours susceptible d'être mis à l'arrêt. C'est d'ailleurs ce qui est arrivé aux 31 versions linguistiques du projet Wikinews, qui, en date du 4 mai 2026, soit 22 ans après le lancement du site anglophone, sont accessibles en mode lecture uniquement. Dans le cas de Wikinews, ce fut le manque d'activité qui apparut comme principale justification de la suspension du projet. Cependant, d'autres raisons pourraient être invoquées, comme le prouve cet épisode de 2005, où, peu de temps après son lancement, la version francophone du recueil de citations Wikiquote a bien risqué de disparaitre. Le projet fut effectivement accusé d’avoir récupéré le contenu d’une base de données soumise à un droit d’auteur, incompatible avec la licence Creative Commons appliquée sur l’ensemble des projets pédagogiques Wikimédia. Lorsqu'une plainte fut adressée à l’association Wikimédia France, pour être ensuite relayée sur le site Méta-Wiki, il fut bien question de fermer le projet. Après de longues discussions, celui-ci fut toutefois maintenu, mais avec pour conditions de repartir de zéro, et d’établir une charte pour garantir la traçabilité des citations reprises par le projet. Voici donc de quoi se faire une idée sur la manière dont les projets pédagogiques, et leurs déclinaisons linguistiques, apparaissent et disparaissent au sein du mouvement. Les exemples repris ci-dessus suffisent effectivement pour comprendre les principes généraux qui sous-tendent leurs créations. En dehors de Méta-Wiki, Wikidata, Wikifunctions, Abstract Wikipédia et Wikimedia Commons, qui ne sont pas des projets de contenu pédagogique à proprement parler, tous les autres projets semblent effectivement provenir d’un désir de spécialisation d’un projet préexistant. L'idée est généralement débattue dans un projet de même langue, avant de relayer la discussion vers le site Méta-Wiki. Si le projet y est jugé pertinent, il fait alors l'objet d'une candidature. Celle-ci doit en effet être acceptée par le groupe de travail des projets frères, repris sous le mandat du comité des affaires communautaires de la Fondation Wikimédia, avant la création du nouveau site. Beaucoup de temps peut s'écouler entre l'idée et le lancement d'un projet, comme en témoigne Wikisources et ses trois ans de maturation. Quant aux nouvelles versions linguistiques, elles doivent être aujourd'hui soumises à l'approbation du comité des langues, avant d'être testées sur les plateformes Incubator, Beta-Wikiversité ou Wikisource multilingue, dans le but de bénéficier d’un site web indépendant. Après ces explications concernant les projets frères et leurs variations linguistiques, il nous reste encore à parler des sites qui ont un lien avec le mot Wiki, soit par leur nom, soit par le logiciel utilisé. En 2024 effectivement, plus de 22 600 d'entre eux étaient répertoriés, dont plus de 95 % sans aucun lien avec le mouvement Wikimédia, en dehors du fait, peut-être, qu’ils utilisaient le logiciel MediaWiki développé par la Fondation Wikimédia. WikiLeaks par exemple, créé par Julian Assange dans le but de publier des documents classifiés provenant de sources anonymes, n’est ni un projet Wikimédia, ni un site collaboratif. Quant au recueil universel et multilingue de guides illustrés WikiHow, si celui-ci fonctionne pour sa part de manière collaborative et avec le logiciel MediaWiki, il n'a pourtant aucun lien avec le mouvement Wikimédia. D'ailleurs, son ergonomie, radicalement différente de celle des projets Wikimédia, permet de le comprendre au premier coup d’œil. En revanche, Wikimini, l'encyclopédie libre pour les enfants, a une apparence tout à fait comparable à celle des projets Wikimédia, alors que le projet n’a jamais été accepté par la Fondation. Quant aux projets WikiTribune et Fandom, l'ambiguïté qu'ils entretiennent avec le mouvement est d'autant plus grande qu'ils ont été créés par Jimmy Wales, le fondateur de Wikipédia et de la Fondation Wikimédia. Cependant, comme ce sont des projets commerciaux, ils ne peuvent en aucun cas être soutenus par une fondation sans but lucratif. Au terme de cette présentation, il ne reste plus qu'à signaler que le mouvement Wikimédia ne fut conscientisé que tardivement par rapport à l'apparition des projets Wikimédia et de leurs différentes versions linguistiques. Pour qu'un sentiment de collectivité se manifeste entre tous ceux-ci, il fallut effectivement attendre qu'une coordination se développe sur la plateforme Méta-Wiki, mais également que de nombreuses rencontres et associations apparaissent en dehors de l'espace numérique. En ce sens, la naissance du mouvement ne fut pas un événement ponctuel, mais plutôt la réalisation d’un long processus de conscientisation. '''Chapitre 9 : La conscientisation du mouvement.''' Avant d'aborder la question de la conscientisation du mouvement, il peut être intéressant de découvrir l'origine étymologique du mot « Wikimédia ». Celui-ci est un mot-valise composé du suffixe « média » et du préfixe « wiki » que l’on doit à cette expression hawaïenne « wiki wiki », qui se traduit en français par l'expression : « vite, vite ». Celle-ci fut récupérée une première fois par Ward Cunningham, le créateur du premier moteur Wiki, avant d'être réutilisée dans les noms inventés pour d'autres logiciels de cette même famille. UseModWiki en est un bel exemple, puisqu'il fut le premier programme utilisé par la firme Bomis pour héberger son projet d’encyclopédie collaborative. Raison pour laquelle, sans doute, le terme « wiki » fut utilisé pour créer le mot Wikipédia, en l'associant au suffixe « pedia » qui fait référence au mot anglais encyclopedia, selon un principe qui fut ensuite repris pour tous les autres projets du mouvement. Le mot Wikimédia, pour sa part, n’est apparu que le seize mars 2003, lors d’une discussion concernant la déclinaison possible de l’encyclopédie en d’autres types de projets éditoriaux participatifs. Durant celle-ci, l’écrivain américain Sheldon Rampton eut l’idée d’associer au terme wiki à celui de « média », afin de mettre en évidence la variété des médias produits et partagés par Wikipédia et ses projets frères. Toutefois, c'est seulement en juin 2008 que Florence Devouard, présidente de la Fondation à cette époque, associe le mot Wikimédia à un mouvement social qu’elle voyait apparaître au sein des projets Wikimédia et de leurs communautés d'usagers. Affirmer pour autant que ce moment précis coïncide avec la naissance du mouvement serait quelque peu arbitraire. Car si l’on peut déterminer plus ou moins facilement l’apparition d’une expression dans des archives, tout le monde sait qu’un mouvement social ne se forme pas en un seul instant. Dans le contexte du de Wikimédia, sa naissance est bien sûr liée à celle du projet Wikipédia, mais également à tout ce qui permit la création de cette encyclopédie libre. Dans une autre perspective encore, on peut dater l'apparition du mouvement Wikimédia au 20 juin 2003, date de la création de la fondation qui porte le même nom. Ou pourquoi pas, associer la création du mouvement à la mise en ligne de la plate-forme Méta-Wiki, qui en représente le principal lieu de coordination. Toujours est-il que l’expression « Wikimedia movement » est bien apparue en juin 2008, sous la plume de Florence Devouard. Cela s'est passé sur la liste de diffusion de la Fondation et peu de temps avant qu'elle quitte son poste de présidente. Dans son message, elle partageait l'idée de placer sous le nom de domaine wikimedia.org un site vitrine de présentation du mouvement Wikimédia qu'elle concevait de la sorte. Le mouvement Wikimédia, comme je l’entends est –  une collection de valeurs partagées par les individus (liberté d’expression, connaissance pour tous, partage communautaire, etc.) –  un ensemble d’activités (conférences, ateliers, wikiacadémies, etc.) –  un ensemble d’organisations (Wikimedia Foundation, Wikimedia Allemagne, Wikimedia Taïwan, etc.), ainsi que quelques électrons libres (individus sans chapitres) et des organisations aux vues similaires. Avec autant de détails et d'explications, un tel message ne pouvait qu'accélérer la prise de conscience au sein du mouvement. Dans tous les cas, il mettait en évidence que les personnes actives dans les projets éditoriaux en ligne ou dans les organismes affiliés, faisaient partie de ce que Ralf Dahrendorf appelle un « quasi-groupe ». Autrement dit, un ensemble d’individus qui ont un mode de vie semblable, une culture commune, mais dont les points communs ne gravitent pas autour d’une prise de conscience de leur position commune dans la relation d’autorité. Après la naissance de Wikipédia et de nombreux projets frères, une dizaine d’années a donc été nécessaire pour que le mouvement Wikimédia prenne conscience de son existence. Aujourd’hui encore, et comme cela a déjà été vu, de nombreuses personnes actives dans les projets pédagogiques ne réalisent toujours pas qu’elles participent aux activités d’un mouvement social. Cela, contrairement aux personnes investies dans les activités en présentiel organisées au sein du mouvement, qui sont généralement plus conscientes de leur engagement. C’est là une raison de croire que le développement de la Fondation Wikimédia et de ses organismes affiliés a joué un rôle important dans l'apparition d'un sentiment d’appartenance. '''Chapitre 10 : La création des organismes affiliés.''' Si c’est grâce à l’arrivée des groupes et des organismes affiliés à la Fondation Wikimédia que l’idée du mouvement est probablement apparue, il est alors intéressant d'en décrire les processus de création. Mais puisque cela représente plusieurs centaines d’instances spécifiques, regroupées en plusieurs catégories détaillées en seconde partie d'ouvrage, aborder ici l’histoire de chacune d’entre elles serait une entreprise beaucoup trop fastidieuse. De plus, s’il existe énormément d’archives numériques concernant la naissance des sites Wikimédia, ce n’est pas le cas pour ces organismes affiliés. Un bon nombre de ceux-ci se sont effectivement formés durant des rencontres ou des réunions hors ligne qui n'ont fait l’objet d’aucun enregistrement. Du reste, une bonne part des échanges effectués au sein de ces associations s'organise par des canaux de communication privés auxquels seuls les membres actifs ont accès. Puisque je suis l'un des membres fondateurs, je me limiterai donc ici à parler de l’association Wikimédia Belgique. Celle-ci fut fondée le huit octobre 2014 en tant qu’association sans but lucratif, avant d'être reconnue le six août 2015 par le conseil d’administration de la Fondation Wikimédia. Après plus de trois ans d’activités et de rencontres et sous l’impulsion de Maarten Deneckere, qui assuma le premier mandat de présidence, nous étions 8 personnes à signer la première version des statuts de l’association. Jusqu’à ce jour, l’objet social de Wikimedia Belgique est d'impliquer tout un chacun dans la connaissance libre. Contrairement à l'association Wikimédia Deutchland, la première à voir le jour en 2004 et qui rassemblait déjà en 2021 plus de 85 000 membres et près de 150 employés, l’association belge n'a qu'une seule employée à temps partiel et 150 membres en 2025. Avant d’être reconnues par le comité d’affiliations chargé de seconder le conseil d’administration de la Fondation, toutes les associations nationales, dites « chapters » en anglais, et toutes les autres organisations affiliées doivent réaliser de nombreuses démarches. Celles-ci consistent à répondre à un ensemble de prérequis qui ont évolué suite à la création d'un comité décisionnel en avril 2006. Ces obligations diffèrent entre les groupes d’utilisateurs et d'utilisatrices et les associations locales ou thématiques. Parmi ceux-ci, on retrouve toutefois : un nombre minimum de membres et de référents, une mission et un règlement d’ordre intérieur conformes aux attentes du mouvement, la remise de plans et de rapports d’activités annuels, etc. On comprend donc qu’il n’est pas évident de créer une nouvelle instance au sein du mouvement. Pour bénéficier du soutien logistique et financier de la Fondation réservé aux organismes affiliés, c’est ainsi toute une série de rapports qu’il faut alors transmettre à divers comités et commissions chargés de leurs évaluations. Cela représente une quantité de tâches administratives qu’il n’est pas toujours facile d’assumer, surtout lorsque les membres de l’organisme affilié sont tous des bénévoles. D’où sans doute cette régulière disparition d’affiliations, pendant que d’autres se créent ou réapparaissent en fonction des énergies et du dynamisme disponibles dans les équipes. Les activités liées à la récolte et à la redistribution des dons offerts au mouvement, ainsi que les autorisations d’usage de marques déposées, contrastent donc avec les valeurs de libre partage et d’autonomie décrites dans les projets pédagogiques. Cela confirme sans doute que la partie hors ligne du mouvement est plus influencée par le système économique qui l'entoure, pendant que la partie en ligne reste plus fidèle à l’héritage transmis depuis la contre-culture des années 1960. Pour conclure cette première partie d'ouvrage, voici donc quelques dernières considérations concernant cet héritage. '''Chapitre 11 : L'héritage d'une contre-culture.''' Au terme de cette première partie d’ouvrage, il devient évident que la révolution numérique, que l’on considère généralement comme une révolution technique, fut aussi, et peut-être avant tout, une révolution sociale et culturelle. Quant à l'histoire de Wikimédia, reprise ici depuis les origines de son encyclopédie jusqu'à l'apparition de ses organismes affiliés, elle nous fit découvrir comment les idées de la contre-culture des années 1960 furent transmises au mouvement. En cas de doute, observons encore que Richard Stallman, celui qui a créé les concepts de licence et d'encyclopédie libre, fut désigné par certains comme le gourou de la contre-culture hacker et le père du système d’exploitation hippie. Une culture hippie, dont il est aussi troublant de constater que le renversement de son logo ressemble étrangement à celui du mouvement Wikimédia. Incontestablement et au travers du mouvement des logiciels libres, le mouvement Wikimédia a donc bien hérité des valeurs produites par les mouvements sociaux des années 1960. Des valeurs qui aujourd'hui contrastent fortement avec la marchandisation et la capitalisation du monde, dont l'espace web ne fait jamais que refléter ce qui se passe dans le reste de la société humaine. Or, ce phénomène ne date pas d'hier. En 2008 déjà, André Gorz, ce philosophe parmi les pères de la décroissance et théoricien de l’écologie politique, constatait déjà que : La lutte engagée entre les logiciels propriétaires et les logiciels libres a été le coup d’envoi du conflit central de l’époque. Il s’étend et se prolonge dans la lutte contre la marchandisation de richesses premières – la terre, les semences, le génome, les biens culturels, les savoirs et compétences communs, constitutifs de la culture du quotidien et qui sont les préalables de l’existence d’une société. De la tournure que prendra cette lutte dépend la forme civilisée ou barbare que prendra la sortie du capitalisme. Dans cette lutte et avec le seul nom de domaine non commercial parmi le top 100 des sites les plus fréquentés du Web, la galaxie Wikimédia apparait donc comme un des derniers lieux numériques de liberté, de partage et d'égalité. Un lieu qui, de plus, est connu mondialement, grâce au succès des plus de 350 déclinaisons linguistiques de Wikipédia, mais sans pour autant récolter d'informations sur l’identité et le comportement des internautes. Une nouvelle information importante donc, quand on sait que cette pratique est considérée, par certains, comme un « nouvel or noir » pompé du Web, pendant que d’autres préfèrent parler de « capitalisme 3.0 » ou de « capitalisme de surveillance ». Évidemment, les enjeux de cette lutte sont difficiles à comprendre. La complexité de l’infrastructure informatique, mais également le fait que tout cela s'inscrit dans une révolution que Rémy Rieffel décrit comme instable et ambivalente, simultanément porteuse de promesse et lourde de menaces, ne facilitent pas les choses. Cela d'autant plus que tout cela se place dans un contexte dans lequel s’affrontent des valeurs d’émancipation, et d’ouverture d’un côté et des stratégies de contrôle et de domination de l’autre. En fait d’ambivalence, il est surprenant d'apprendre, par exemple, que Jimmy Wales, créateur de Wikipédia et de la fondation Wikimédia, est un adepte de l’objectivisme, alors que cette philosophie voit le capitalisme, comme un idéal de société, et l’égoïsme rationnel, comme une morale. Puis, concernant l'instabilité du numérique, il y a ces appels répétés de Tim Berners-Lee au sujet de la redécentralisation et de la régulation d'un espace web, qu'il avait conçu dans un esprit tout à fait opposé. Quant aux pionniers d'Internet, ils n'ont probablement pas imaginé que leur création permettrait un jour, à des milliards d’objets connectés, de rapporter, rien qu'en France et en 2021, plus de 2,6 milliards d’euros. Concernant le contrôle et au-delà de ce qui est opéré par les firmes commerciales, c'est bien sûr du côté des États qu'il faut porter son attention. Face à un mouvement émancipateur comme l'est Wikimédia, par moins de 18 pays ont déjà censuré Wikipédia et parfois même l'ensemble des projets frères. Ce fut le cas par exemple pour la Turquie, la Russie, l'Iran, mais également le Royaume-Uni, la France et l'Allemagne, et c'est même le cas de manière permanente en Chine, depuis 2004. Dans certains cas, des procédures juridiques ont été utilisées pour intimider les membres du mouvement. C'est arrivé en France lorsque le directeur de l'association Wikimédia fut menacé de poursuites pénales par la Direction Centrale du Renseignement Intérieur, après un refus de supprimer un article qui traitait d’une station militaire dans Wikipédia. Par chance, ce qui s'est passé en France ne dépassa pas le stade de l'intimidation. En Biélorussie cependant, Mark Bernstein, un contributeur aux projets Wikimédia, fut condamné à quinze jours de prison ferme, assortis de trois ans d’assignation à résidence, pour des propos tenus au sujet de la guerre en Ukraine. Tout cela alors qu'actuellement, ce sont les conservateurs à la tête des États-Unis qui « veulent la peau de Wikipédia » en cherchant à obtenir l'identité réelle de certains contributeurs. Le contrôle et le non-respect de la vie privée font ainsi appel à la figure emblématique du lanceur ou de la lanceuse d'alerte, dont la posture contestataire fait penser aux personnes actives dans la contre-culture des années 1960. Parmi ceux-ci, on dit de Julian Assange, Edward Snowden et Chelsea Manning, qu'ils « ont perdu leur liberté pour défendre la nôtre ». De manière similaire, on pourrait donc aussi dire que les Wikimédiens Aaron Swartz, Bassel Khartabil, Pavel Pernikov, Ihor Kostenko et Mark Bernstein, se sont sacrifiés pour la liberté, le partage et la vérité. Dans Wikimédia et comme ce fut expliqué dans l'introduction de cet ouvrage, une alerte peut prendre la forme d'un appel à commentaires en réaction à une décision ou une situation observée au sein du mouvement. C'est même là une pratique institutionnalisée, qui fait l'objet d'une procédure d'accompagnement et de suivi. Toutes ces alertes concernent les dérives de certains projets, mais également de la Fondation et de certaines associations, qui peinent parfois à respecter les pratiques et les valeurs développées dans la partie en ligne du mouvement. Ce qui n'empêche toutefois pas les projets éditoriaux d'avoir leurs propres règles et des recommandations, ni de voir apparaitre, en 2020 et dans l'ensemble du mouvement, un code de conduite universel, qui détermine le référentiel minimum des comportements acceptables et inacceptables. L'idéologie transmise à Wikimédia, et décrite en partie par Steven Levy dans son ouvrage L’Éthique des hackers, est donc plus subtile qu'un simple refus d'autorité. Dans un esprit de partage, d'ouverture, de transparence, de liberté, d'égalité et d'autonomie, c'est en fin de compte une structure très complexe, tout en étant cosmopolite et mondiale, que le mouvement réussit à mettre en place. Une organisation qui, comme nous allons le découvrir dans la deuxième partie de ce livre, apparait très inspirante dans la manière de faire communauté, au sein d'un monde toujours plus global et numérique. {{Autocat}} 1yrg0ys5j8e59kl36109axlqgc46hh2