Aller au contenu

Fonctionnement d'un ordinateur/Les circuits de calcul flottant

Un livre de Wikilivres.

Dans le chapitre précédent, nous avons vu les circuits de calcul pour les nombres entiers. Il est maintenant temps de voir les circuits pour faire des calculs, mais avec des nombres flottants. Nous allons surtout nous concentrer sur les nombres flottants au format IEEE754, mais feront un aparté sur les flottants logarithmiques. Rappelons que la norme IEEE754 précise le comportement de 5 opérations: l'addition, la soustraction, la multiplication et la division.

Normalisation in circuit

Un point important est que les circuits de calcul flottants effectuent des calculs, mais aussi des tâches de normalisation et d'arrondis. La normalisation corrige 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.

Avant le calcul, il y a aussi 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). Pour la multiplication et la division, l'étape de prénormalisation ne fait pas autre chose. Mais pour l'addition et la soustraction, elle a une seconde fonction : corriger les deux opérandes pour qu'elles soient additionnables. En effet, on peut additionner deux flottants très simplement si leurs deux exposants sont égaux. D'où une étape pour mettre les deux opérandes au même exposant, en modifiant leur mantisse, avant de faire le calcul.

Il faut noter que 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.

Les multiplications/divisions flottantes

[modifier | modifier le wikicode]

Paradoxalement, les multiplications, divisions et racines carrées sont relativement simples à calculer avec des nombres flottants, là où l'addition et la soustraction sont plus complexes. Aussi, nous allons d'abord parler des opérations de multiplications et divisions, avant de poursuivre avec les addition et soustraction, en enfin de terminer avec les procédés de normalisation, arrondis et prénormalisation.

La multiplication flottante

[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. Le circuit est donc composé d'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 que l'additionneur-soustracteur utilisé est un additionneur-soustracteur spécifiques à cette représentation.
  • 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.
Multiplieur flottant avec normalisation

La division flottante

[modifier | modifier le wikicode]

La division fonctionne sur le même principe que la multiplication, si ce n'est que les calculs sont quelque peu différents : 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 :

On voit que les mantisses sont divisées entre elles, tandis que les exposants sont soustraits.

La racine carrée flottante

[modifier | modifier le wikicode]

Le calcul de la racine carrée d'un flottant est relativement simple. Par définition, la racine carrée d'un flottant vaut :

La racine d'un produit est le produit des racines :

Vu que , on a :

On voit qu'il suffit de calculer la racine carrée de la mantisse et de diviser l'exposant par deux (ou le décaler d'un rang vers la droite ce qui est équivalent). Voici le circuit que cela doit donner :

Racine carrée FPU

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 ce n'est pas le cas pour la plupart des calculs flottants qu'on souhaite faire, ce qui n’empêche cependant pas de ruser. L'idée est de mettre les deux flottants au même exposant, 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).

Crcuit 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 flottants

[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, mais il doit aussi passer par d'autres étapes dites de normalisation.

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 une mémoire ROM. Ce qui est réalisé dans quelques unités flottantes.

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.

Le circuit de normalisation/arrondi final

[modifier | modifier le wikicode]

Le circuit complet, qui effectue à la fois normalisation et arrondis est le suivant :

Circuit de normalisation-arrondi

Les flottants logarithmiques

[modifier | modifier le wikicode]

Maintenant, nous allons fabriquer une unité de calcul 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.

Nous avions dit dans le chapitre sur le codage des nombres que l'utilité de cette représentation est de simplifier certains calculs, comme les multiplications, divisions, puissances, etc. Eh bien, vous allez rapidement comprendre pourquoi dans cette section. Nous allons commencer par voir les deux opérations de base : la multiplication et la division. Celles-ci sont en effet extrêmement simples dans cet encodage, bien plus que l'addition et la soustraction. C'est d'ailleurs la raison d'être de cet encodage : simplifier fortement les calculs multiplicatifs, quitte à perdre en performance sur les additions/soustractions.

La multiplication et la division de deux flottants logarithmiques

[modifier | modifier le wikicode]

Pour commencer, il faut se souvenir d'un théorème de mathématique sur les logarithmes : 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. Dans les calculs précédents, il suffit de se rappeler que diviser par , c'est multiplier par . Or, il faut se rappeler que . On obtient alors, en combinant ces deux expressions :

La division s'est transformée en simple soustraction. Dans ces conditions, une unité de calcul logarithmique devant effectuer des multiplications et des divisions est constituée d'un simple additionneur/soustracteur et de quelques (ou plusieurs, ça marche aussi) circuits pour corriger le tout.

L'addition et la soustraction de deux flottants logarithmiques

[modifier | modifier le wikicode]

Pour l'addition et la soustraction, la situation est beaucoup plus corsée, vu qu'il n'y a pas vraiment de formule mathématique pour simplifier le logarithme d'une somme. Dans ces conditions, la seule solution est d'utiliser une mémoire de précalcul, comme vu au début du chapitre. Et encore une fois, il est possible de réduire la taille de mémoire ROM de précalcul en utilisant des identités mathématiques. L'idée est de transformer l'addition en une opération plus simple, qui peut se pré-calculer plus facilement.

Pour cela, partons de la formule suivante, qui pose l'équivalence des termes suivants :

Vu que le logarithme d'un produit est égal à la somme des logarithmes, on a :

Pour rappel, les représentations de x et y en flottant logarithmique sont égales à et . En notant ces dernières et , on a :

Par définition, et . En injectant dans l'équation précédente, on obtient :

On simplifie la puissance de deux :

On a donc :

, avec f la fonction adéquate.

Pour la soustraction, on a la même chose, sauf que les signes changent, ce qui donne :

, avec g une fonction différente de f.

On vient donc de trouver la formule qui permet de faire le calcul, le seul obstacle étant la fonction f et la fonction g. Heureusement, le terme de droite peut se pré-calculer facilement, ce qui donne une table beaucoup plus petite qu'avec l'idée initiale. Dans ces conditions, l'addition se traduit en :

  • un circuit qui additionne/soustrait les deux opérandes ;
  • une table qui prend le résultat de l'additionneur/soustracteur et fournit le terme de droite ;
  • et un autre additionneur pour le résultat.

Pour implémenter les quatre opérations, on a donc besoin :

  • de deux additionneurs/soustracteur et d'un diviseur pour l'addition/soustraction ;
  • de deux autres additionneurs/soustracteur pour la multiplication et la division ;
  • et d'une ROM.

Il est bon de noter qu'il est tout à fait possible de mutualiser les additionneurs pour la multiplication et l'addition. En rajoutant quelques multiplexeurs, on peut faire en sorte que le circuit puisse se configurer pour que les additionneurs servent soit pour la multiplication, soit pour l'addition. On économise en peu de circuits.

Unité de calcul logarithmique