Aller au contenu

Fonctionnement d'un ordinateur/Les circuits de calcul flottant

Un livre de Wikilivres.

Après avoir vu les circuits de calcul pour les nombres entiers, il est temps de voir les circuits de calculs pour des nombres flottants. Nous allons nous concentrer sur les nombres flottants au format IEEE754, avant de faire un aparté sur les flottants logarithmiques.

Unité de calcul flottante, intérieur

Il est souvent dit qu'un processeur incorpore une unité de calcul spécialisée dans les calculs flottants, appelée la Floating Point Unit, ce qui se traduirait en unité de calcul flottante. Dans la réalité, les processeurs modernes incorporent plusieurs circuits distincts : un pour multiplier deux flottants, un autre pour additionner deux flottants, et éventuellement un troisième pour la division flottante. Ils sont implémentés avec des circuits séparés, comme le sont les ALU et les circuits multiplieurs/diviseurs.

Les multiplications/divisions flottantes

[modifier | modifier le wikicode]

Paradoxalement, les multiplications, divisions et racines carrées sont plus simples que l'addition, avec des nombres flottants. Aussi, nous allons d'abord parler des opérations de multiplications et divisions, avant de poursuivre avec les additions et soustractions, en enfin de terminer avec les procédés de normalisation, arrondis et prénormalisation.

Avant le calcul, il y a une étape de prénormalisation, qui gère le bit implicite des mantisses. Elle détermine si ce bit vaut 0 (flottants dénormaux) ou 1 (les flottants normaux), puis l'ajoute aux mantisses. Pour la multiplication et la division, l'étape de prénormalisation ne fait pas autre chose. Mais pour l'addition et la soustraction, elle est plus complexe, comme on le verra plus tard.

Les circuits multiplieurs/diviseurs flottants

[modifier | modifier le wikicode]

Prenons deux nombres flottants de mantisses et et les exposants et . Leur multiplication donne :

On regroupe les termes :

On simplifie la puissance :

En clair, multiplier deux flottants revient à multiplier les mantisses et additionner les exposants, ce qui demande un additionneur-soustracteur et un multiplieur. Il faut cependant penser à plusieurs choses pas forcément évidentes.

  • Premièrement, il faut ajouter les bits implicites aux mantisses avant de les multiplier, ce qui est le rôle de l'étape de pré-normalisation.
  • Deuxièmement, il faut se rappeler que les exposants sont encodés en représentation par excès, ce qui fait qu'il faut utiliser un additionneur-soustracteur en représentation par excès.
  • Troisièmement, il faut calculer le bit de signe du résultat à partir de ceux des opérandes.
  • Enfin, il ne faut pas oublier de rajouter les étapes de normalisation et d'arrondis, dont on parlera plus bas.
Multiplieur flottant avec normalisation

La division fonctionne sur le même principe que la multiplication, si ce n'est que les exposants sont soustraits et que les mantisses sont divisées. Pour le démontrer, prenons deux flottants et et divisons le premier par le second. On a alors :

On applique les règles sur les fractions :

On simplifie la puissance de 2 :

La multiplication entière réalisée par l'unité de calcul flottante

[modifier | modifier le wikicode]

Vous remarquerez qu'un multiplieur flottant contient un multiplieur entier pour multiplier les mantisses. Sachez qu'il est possible de n'utiliser qu'un seul multiplieur entier pour faire à la fois les multiplications entières et flottantes. Cette optimisation a été utilisée sur plusieurs processeurs commerciaux. Par exemple, les processeurs Atom d'Intel utilisaient cette optimisation : les multiplications entières étaient réalisées dans un multiplieur entier partagé avec l'unité de calcul flottante. Il en est de même sur les processeurs Athlon d'AMD, sortis dans les années 2000.

Mais cela ne fonctionnait que sur les processeurs 32 bits, pas sur les 64 bits. Pour comprendre pourquoi, il faut savoir que les processeurs modernes gèrent des nombres flottants simple et double précision, à savoir codés sur 32 et 64 bits. Il y a un seul multiplieur, capable de gérer des flottants double précision, qui peut le plus peut le moins. Avec des flottants double précision de 64 bits, les mantisses font 52 bits, ce qui fait un multiplieur de 53 bits. C'est suffisant pour un processeur 32 bits, pas assez sur un processeur 64 bits.

Ajoutons cependant une petite nuance. Sur les anciens processeurs x86 des PC, les flottants faisaient 80 bits, avec une mantisse de 64 bits, ce qui est assez à la fois pour les processeurs 32 et 64 bits. Malheureusement, les processeurs 64 bits avaient un budget en transistor suffisant pour ne pas appliquer cette optimisation. Pour des raisons diverses, il était préférable d'avoir un multiplieur entier séparé du multiplieur flottant.

L'addition et la soustraction flottante

[modifier | modifier le wikicode]

La somme de deux flottants se calcule simplement si les exposants des deux opérandes sont égaux : il suffit alors d'additionner les mantisses. Mais que faire si les deux exposants sont différents ? L'astuce est de mettre les deux flottants au même exposant sans en changer leur valeur, de les mettre à l'échelle. L'exposant choisi étant souvent le plus grand exposant des deux flottants. Une fois mises à l'échelle, les deux opérandes sont additionnées, et le résultat est normalisé pour donner un flottant.

Suivant les signes, il faudra additionner ou soustraire les opérandes : additionner une opérande positive avec une négative demande en réalité de faire une soustraction, de même que soustraire une opérande négative demande en réalité de l'additionner. Il faut donc ajouter, avant l'additionneur, un circuit qui détermine s'il faut faire une addition ou une soustraction, en fonction du bit de signe des opérandes, et de s'il faut faire une addition ou une soustraction (opcode de l'opération voulue).

Circuit d'addition et de soustraction flottante.

Le circuit de pré-normalisation

[modifier | modifier le wikicode]

La mise des deux opérandes au même exposant s'appelle la pré-normalisation. L'exposant final est choisit parmi les deux opérandes : on prend le plus grand exposant parmi des deux. L'opérande avec le plus grand exposant reste inchangée, elle est conservée telle quelle. Par contre, il faut pré-normaliser l'autre opérande, celui avec le plus petit exposant. Et pour cela, rien de plus simple : il suffit de décaler la mantisse vers la droite, d'un nombre de rangs égal à la différence entre les deux exposants.

Pour faire ce décalage, on utilise un décaleur et un circuit qui échange les deux opérandes. Le circuit d'échange a pour but d'envoyer le plus petit exposant dans le décaleur et est composé de quelques multiplexeurs. Il est piloté par un comparateur qui détermine quel est le nombre avec le plus petit exposant. Nous verrons comment fabriquer un tel comparateur dans le chapitre suivant sur les comparateurs.

Circuit de mise au même exposant.

Précisons que le comparateur et le soustracteur peuvent être fusionnés, car un comparateur est en réalité un soustracteur amélioré. Une manière alternative est la suivante. En premier lieu, on soustrait les exposants pour déterminer de combien décaler la mantisse. Le résultat de la soustraction est ensuite envoyé à un circuit qui vérifie si le résultat est positif ou négatif, en vérifiant le bit de poids fort du résultat. Si le résultat est positif, la première opérande est plus grande que la seconde, c'est la seconde opérande qu'il faut pré-normaliser. Si le résultat est négatif, c'est la première opérande qu'il faut prénormaliser.

Circuit de prénormalisation d'un additionneur flottant

La normalisation et les arrondis

[modifier | modifier le wikicode]

Calculer sur des nombres flottants peut sembler trivial, mais les mathématiques ne sont pas vraiment d'accord avec cela. En effet, le résultat d'un calcul avec des flottants n'est pas forcément un flottant valide. Il doit subir quelques transformations pour être un nombre flottant : il doit souvent être arrondi, et doit auissi passer par d'autres étapes dites de normalisation.

Normalisation in circuit

Elles corrigent le résultat du calcul pour qu'il rentre dans un nombre flottant. Par exemple, si on multiplie deux flottants de 32 bits, l'exposant et la mantisse du résultat sont calculés séparément et les concaténer ne donne pas forcément un nombre flottant 32 bits. Diverses techniques de normalisation et d'arrondis permettent de corriger l'exposant et la mantisse pour donner un flottant 32 bit correct. Et elles auront leur section dédiée.

La normalisation et les arrondis sont gérés différemment suivant le format de flottant utilisé. Les flottants les plus courants suivent la norme IEEE754, où normalisation et arrondis sont standardisés. Mais d'autres formats de flottants exotiques peuvent suivre des règles différentes.

La normalisation

[modifier | modifier le wikicode]

La normalisation gère le bit implicite. Le résultat en sortie d'un circuit de calcul n'a pas forcément son bit implicite à 1. Prenons l'exemple suivant, où on soustrait deux flottants qui ont des mantisses codées sur 8 bits - le format de flottant n'est donc par standard. On soustrait les deux mantisses suivantes, le chiffre entre parenthèse est le bit implicite : (1) 1100 1100 - (1) 1000 1000 = (0) 0100 0100.

Le résultat a un bit implicite à 0, ce qui donne un résultat dénormal. Mais il est parfois possible de convertir ce résultat en un flottant normal, à condition de corriger l'exposant. L'idée est, pour le cas précédent, de décaler la mantisse de deux rangs : (0) 0100 0100 devient (1) 0001 0000. Mais décaler la mantisse déforme le résultat : le résultat décalé de deux rangs vers la gauche multiplie le résultat par 4. Mais on peut compenser exactement le tout en corrigeant l'exposant, afin de diviser le résultat final par 4 : il suffit de soustraire deux à l'exposant !

Le cas général est assez similaire, sauf que l'on doit décaler la mantisse par un nombre de rang adéquat, pas forcément 2, et soustraire ce nombre de rangs à l'exposant. Pour savoir de combien de rangs il faut décaler, il faut compter le nombre de zéros situés de poids fort, avec un circuit spécialisé qu'on a vu il y a quelques chapitres, le circuit de CLZ (Count Leading Zero). Ce circuit permet aussi de détecter si la mantisse vaut zéro.

Circuit de normalisation.

Une fois ce résultat calculé, il faut faire un arrondi du résultat avec un circuit d'arrondi. L'arrondi se base sur les bits de poids faible situés juste à gauche et à droite de la virgule., ce qui demande d'analyser une dizaine de bits tout au plus. Une fois les bits de poids faible à gauche de la virgule sont remplacé, les bits à droite sont éliminés.

L'arrondi peut être réalisé par un circuit combinatoire, mais le faible nombre de bits d'entrée rend possible d'utiliser ce qui s'appelle une mémoire de précalcul. Avec cette technique, le circuit combinatoire est remplacé par une ROM équivalente. Les entrées du circuit combinatoire deviennent l'entrée d'adresse de la ROM, les sorties du circuit deviennent la sortie de donnée de la ROM. Pour une entrée identique, les deux donneront les mêmes sorties.

Circuit d'arrondi flottant basé sur une ROM.

Malheureusement, il arrive que ces arrondis décalent la position du bit implicite d'un rang, ce qui se résout avec un décalage si cela arrive. Le circuit de normalisation contient donc de quoi détecter ces débordements et un décaleur. Bien évidemment, l'exposant doit alors lui aussi être corrigé en cas de décalage de la mantisse.

Circuit de postnormalisation.

Les flottants logarithmiques

[modifier | modifier le wikicode]

Maintenant, nous allons fabriquer une FPU pour les flottants logarithmiques. Nous avions vu les flottants logarithmiques dans le chapitre Le codage des nombres, dans la section sur les flottants logarithmiques. Pour résumer rapidement, ce sont des flottants qui codent uniquement un bit de signe et un exposant, mais sans la mantisse (qui vaut implicitement 1). L'exposant stocké n'est autre que le logarithme en base 2 du nombre codé, d'où le nom donné à ces flottants. Au passage, l'exposant est stocké dans une représentation à virgule fixe. L'utilité de cette représentation est de simplifier fortement les multiplications et divisions, quitte à perdre en performance sur les additions/soustractions.

Pour commencer, il faut savoir que le logarithme d'un produit est égal à la somme des logarithmes. Dans ces conditions, une multiplication entre deux flottants logarithmiques se transforme en une simple addition d'exposants.

Le même raisonnement peut être tenu pour la division, sauf que l'addition est remplacée par une soustraction :

Pour l'addition et la soustraction, le calcul se fait avec cette formule :

, avec F une fonction appelée le logarithme gaussien.

Pour la soustraction, on a la même chose, sauf que la fonction change, ce qui donne :

, avec G une fonction différente de F.

Le calcul demande donc de soustraire les deux opérandes, de calculer la fonction F ou G, puis d'additionner la première opérande. Le toute demande donc un soustracteur, un additionneur et une mémoire de précalcul pour la fonction F/G. Il est possible de mutualiser les additionneurs pour la multiplication et l'addition, en rajoutant quelques multiplexeurs.

Unité de calcul logarithmique