Cette bête méconnue, le COPROCESSEUR ! ..--~~--..__..--~~--..__..--~~--..__.. [Et en assembleur !] Alors, je vous préviens tout de suite, je ne vais pas me lancer dans la description des différents formats utilisés pour coder les nombres flottants en binaire, mais je vais entrer dans le vif du sujet et directement passer à la pratique pure et dure ! (Cris de joie et d'approbation des débutants et grognements chez ceux qui voulaient apprendre ça ;). De toute façon, vous trouverez les spécifications techniques dans les docs d'Intel, ou l'excellent FaqSys. Avant de vous expliquer les instructions, je dois quand même vous dire quels sont les principes de fonctionnement du coprocesseur. En premier, il y a la pile. Le 387 a sa propre petite pile, tout à fait séparée du reste du CPU ou de la RAM, et le seul moyen d'y accéder est d'utiliser les instructions du FPU. La pile contient 8 nombres à virgule flottante de 80 bits, nommés ST, ST(1),..,ST(7). ST est considéré comme le haut de la pile. Toutes les instructions comme sin(), cos(), etc fonctionnent seu- lement avec des valeurs stockées dans la pile. Donc, il faudra charger les valeurs sur la pile,exécuter l'instruction puis sauver le résultat en mémoire. Précisions : les exemples utilisent la notation de TASM, qui diffère légèrement de celle de NASM. Charger des valeurs dans la pile ================================ En fait, quand vous chargez une valeur dans le FPU, vous la "pushez" sur la pile, dans ST. Mais avant ça, ST est déplacé vers ST(1), ST(1) vers ST(2), etc. S'il y avait quelque chose dans ST(7), la valeur est perdue (stack overflow). Il est impossible de charger un registre du FPU depuis un registre du CPU, la valeur doit venir de la mémoire ou d'un autre registre du FPU. Plusieurs types de valeurs peyuvent être chargés : * signés 16, 32 ou 64 bits entiers * signés 16, 32 ou 64 bits réels (virgule flottante) * BCD 16, 32 ou 64 bits * un autre élément de la pile du FPU Voici quelques exemples : fld st(3) ; charge st(4) dans st fld dword ptr [nombre1] ; charge un réel 32 bits fild dword ptr [ebx] ; charge un entier 32 bits fbld qword ptr [ebx] ; charge un BCD 64 bits Il y a aussi d'autres commandes pour charger un nombre comme FLDPI (qui charge pi), FLDZ (charge 0), FLD1 (charge 1), FLDL2E (log2(e)), etc. Toutes les valeurs charg‚es sont converties en r‚els 80 bits. Récupérer une valeur du FPU =========================== Une fois les calculs terminés, vous devez récupérer le résultat se trouvant encore sur la pile du FPU. Cela fonctionne de la même manière que pour charger une valeur, avec une légére différence : vous pouvez choisir si vous voulez ou non retirer la valeur de la pile après l'avoir sauvée. Si l'instruction se termine par 'p', elle est "poppée". Exemples : fst dword ptr [nombre2] ; copie ST en mémoire fist dword ptr [ebx] ; convertit ST en entier et le copie en ; mémoire fstp dword ptr [ebx] ; = fst + "poppe" ensuite la valeur fistp dword ptr [edx] ; = fist + "poppe" la valeur Maintenant, calculer ==================== Vous devez en avoir marre de ne toujours pas savoir comment on calcule 1+1 avec le coprocesseur. Patientez, ça va venir. Bon, alors voyons l'addition. Dans tout ce qui va suivre, un opé- rande peut être un registre du FPU aussi bien qu'une adresse mémoire. Il faut faire attention à une chose, lorsqu'on donne DEUX opérandes, le résultat est gardé dans le DEUXIEME, contrairement au fonctionement du CPU. Fadd existe sous trois formes : fadd opérande1, opérande2 ; ajoute opérande1 à opérande2 fadd opérande ; ajoute opérande à ST fadd ; ajoute ST à ST(1) Une variante de cette instruction est faddp. La seule différence est que cette instruction poppe un nombre de la pile. Elle est surtout utile sans opérande pour récupérer la somme dans ST (qui sans cela se retrouve dans ST(1)). Le principe de fonctionnement est le même pour fsub (et fsubp), fmul (fmulp), fdiv (fdivp). Existent aussi les instructions fiadd, fisub, fimul et fidiv, qui travaillent sur des entiers. Exemples ======== Additionner N1 avec N2 et enregistrer le résultat dans N3. Diviser ensuite N3 par N4 et sauver le résultat dans N5. Toutes les variables sont globales, et en float. ; op état FPU fld dword ptr [N1] ; On charge N1 N1 fld dword ptr [N2] ; On charge N2 N2 | N1 faddp ; + et on vire N2 qui traine N1+N2 fst dword ptr [N3] ; on enregistre dans N3 N1+N2 fld dword ptr [N4] ; on charge N4 N4 | N1+N2 fdivp ; on / et on vire N4 (N1+N2)/N4 fstp dword ptr [N5] ; on sauve ca dans N5 (+pop) - Améliorations de l'exemple ========================== Nous allons effectuer les mêmes opérations mais d'une manière plus performante. Il est en effet possible, pour certaines opérations, d'utiliser directement une valeur présente en mémoire, plutôt que de devoir chaque fois la charger. Voyons ce que ca donne. ; op etat FPU fld dword ptr [N1] ; On charge N1 N1 fadd dword ptr [N2] ; On y ajoute N2 N2+N1 fst dword ptr [N3] ; on enregistre dans N3 N1+N2 fdiv dword ptr [N4] ; on divise par N4 (N1+N2)/N4 fstp dword ptr [N5] ; on sauve ca dans N5 (+ pop) - L'exemple est un peu court pour bien montrer l'avantage qu'il est possible de retirer, mais lors de calculs complexes, il est important de bien structurer (parfois même réorganiser) l'ordre du calcul. Conclusion ========== Maintenant que vous connaissez les bases, et comme j'ai la flemme de vous expliquer toutes les instructions, la meilleur chose que je puisse vous suggérer est de partir d'un document qui reprend toutes les instructions, ou un livre et de les découvrir. Ce petit tutorial avait uniquement pour but de vous familiariser avec le _principe_ de fonctionnement du FPU. Allergy Allergy@alrj.org