Fonctionnement d'un ordinateur/Les architectures à pile et mémoire-mémoire
Les chapitres précédents nous ont appris l'existence des architectures à accumulateur, qui se distinguent des architectures à registres généraux (qui ont des registres généraux ou spécialisés pour stocker temporairement des données). Dans ce chapitre, nous allons voir d'autres architectures qui se distinguent à la fois des architectures à registres généraux, et à accumulateur. Elles ont pour particularité qu'elles n'ont pas de registres pour les données, seulement un program counter et quelques registres dans le genre. Elles sont elles-mêmes subdivisées en architecture mémoire-mémoire et à pile, pour des raisons qu'on expliquera dans ce chapitre.
L'absence de registre a de nombreuses conséquences. La première est qu'elle impacte les instructions d'accès mémoire. Toutes les architectures précédentes disposent d'instructions d'accès mémoire, mais elles ne sont pas les mêmes selon l'architecture canonique considérée. Par exemple, les instructions LOAD et STORE n'existent que sur les architectures avec au moins un registre, à savoir les architectures à accumulateur et à registre. Les autres architectures n'ont pas d'instruction LOAD/STORE.
| Classe d'architecture | Nombre de registres pour les données | LOAD et STORE |
|---|---|---|
| Architecture mémoire-mémoire | Aucun. | Non |
| Architecture à pile | ||
| Architecture à accumulateur | Un registre appelé l'accumulateur. | Oui, accumulateur adressé implicitement |
| Architecture à registres | Plusieurs registres dits généraux et/ou spécialisés. | Oui, registres adressés explicitement |
Maintenant que ces préliminaires ont été abordés, voyons dans le détail les architectures mémoire-mémoire et à pile.
Les architectures mémoire-mémoire
[modifier | modifier le wikicode]
Les toutes premières machines n'avaient pas de registres pour les données, pas de registres généraux. Attention, cependant : le program counter existe toujours, de même que le registre d'état et le pointeur de pile. Les instructions n'accédaient qu'à la mémoire RAM ou ROM : on parle d'architectures mémoire-mémoire.
Le défaut des architectures mémoire-mémoire est qu'elles font beaucoup d'accès mémoire, du fait de l'absence de registres. Aussi, la performance dépendait grandement de la performance de la mémoire RAM. Au début de l'informatique, le processeur et la mémoire RAM avaient des performances similaires, ce qui rendait ces architectures viables. Mais depuis que la mémoire est devenue très lente comparé au processeur, ce genre d'architectures est tombé en désuétude.
Un autre défaut de ces processeurs est que leurs instructions de calcul effectuent plusieurs accès mémoire. Une instruction dyadique fait trois accès mémoire : un par opérande, un pour enregistrer le résultat. Et il faut séquencer ces accès mémoire à l'intérieur de l'instruction, ce qui est complexe et demande d'ajouter des registres internes au processeur qui sont cachés du programmeur.
La microarchitecture d'une architecture mémoire-mémoire
[modifier | modifier le wikicode]La microarchitecture d'une architecture mémoire-mémoire est assez simple. Elle contient une unité de calcul, des registres d’interfaçage avec la mémoire et une unité de contrôle. Elle n'a pas de registres, à part un program counter intégrés au séquenceur. La seule difficulté est d'incorporer de quoi gérer les opérations dyadiques et l'adressage mémoire indirect.


La première difficulté est que les opérations dyadiques lisent deux opérandes en RAM et écrivent le résultat en RAM. Mais le bus de donnée ne permet qu'une seule lecture ou écriture à la fois. Les accès mémoire doivent donc s'exécuter l'un après l'autre et doivent être séquencés. Les opérations mémoire-mémoire dyadiques s'exécutent donc en plusieurs micro-opérations. En effet, lire deux opérandes dans la mémoire se fait forcément en deux micro-opérations mémoire consécutives, sans compter les micro-opérations pour calculer le résultat et l'enregistrer en RAM.
Le séquenceur est conçu pour, mais il reste une difficulté : un seul registre d'interfaçage ne permet que le transfert d'un opérande. Lire une seconde opérande en RAM demande de rajouter un second registre d'interfaçage, qui est relié à l'autre entrée de l'ALU. Écrire le résultat en mémoire est géré en rajoutant un troisième registre d'interfaçage, relié à la sortie de l'ALU. Le séquenceur relie le registre d’interfaçage adéquat au bus mémoire à chaque étape d'une instruction.
La seconde difficulté est la gestion des pointeurs. Pour gérer les pointeurs, ces ordinateurs utilisent le mode d'adressage absolu indirect, absolument nécessaire sur des architectures. Avec ce mode d'adressage, l'instruction incorpore une adresse mémoire, mais ce n'est pas l'adresse de la donnée voulue : c'est l'adresse du pointeur, qui lui pointe vers la donnée. Il était systématiquement supporté sur toutes les architectures mémoire-mémoire. Elles supportaient aussi ses variantes à pré– ou post-incrément, où le pointeur était incrémenté/décrémenté automatiquement à chaque accès.
Avec ce mode d'adressage, La lecture de l'opérande se fait en deux étapes. Premièrement, le processeur extrait l'adresse de l'instruction et effectue une lecture pour récupérer le pointeur. Deuxièmement, la donnée lue, le pointeur, est envoyée sur le bus d'adresse pour récupérer la donnée. Le tout se fait en faisant communiquer les registres d’interfaçage de donnée et d'adresse, quelques multiplexeurs suffisent.

L'exemple de l'ordinateur AT&T Hobbit
[modifier | modifier le wikicode]Un cas intéressant d'architecture de ce genre est celle du processeur AT&T Hobbit. L'origine de ce processeur se trouve dans un projet de processeur abandonné par AT&T. Le processeur en question, la C-machine, était un processeur spécifiquement conçus pour le langage de programmation C. Et pour coller le plus possible à ce langage, le processeur n'utilisait pas de registres, seulement des adresses mémoires.
Vu que les programmes codés en C manipulent beaucoup la pile d'appel, le processeur intégrait un cache de pile, spécialisé pour les accès à la pile d'appel. Il contenait 64 données de 4 octets. Il s'agit bien d'un cache, le processeur accède à la pile d'appel en mémoire RAM, mais le cache de pile en a une copie partielle et il intercepte les accès à la RAM, c'est lui qui fournit la donnée demandée s'il la contient. L'usage d'un cache de pile est assez spécifique aux architectures mémoire-mémoire et aux architectures à pile, même s'il doit exister quelques exceptions.
Les architectures à pile
[modifier | modifier le wikicode]
Les architectures à pile, aussi appelées machines à pile, sont un cas particulier d'architectures mémoire-mémoire. Leur nom trahit le fait qu'elles gèrent nativement une pile semblable à la pile d'appel, sauf qu'elle mémorise les opérandes des calculs et leur résultat. Une instruction de calcul prend les opérandes qui sont au sommet de la pile. L'instruction dépile automatiquement les opérandes qu'elle utilise et empile son résultat. Le processeur peut copier des opérandes dans la pile ou en retirer grâce à des instructions PUSH et POP qu'on analysera sous peu.
La séparation entre pile d'opérande et pile d'appel
[modifier | modifier le wikicode]Sur les machines à pile, il faut distinguer la pile d'appel et la pile d'opérande. La première est la pile pour les adresses de retour des fonctions, l'autre est une pile séparée utilisée pour les opérandes des instructions.
Il a existé des machines à pile avec une pile unique, qui servait à la fois de pile d'opérande et de pile d'appel. Elles étaient assez peu performantes. Mais la plupart des designs existants utilisent deux piles séparées, complètement distinctes. En clair, la pile d'appel est gérée à part de la pile d'opérande. La pile d'opérande est en mémoire RAM, sauf optimisation, pas forcément la pile d'appel. La pile d'appel est soit placée dans une mémoire séparée, soit placée dans la mémoire RAM principale. Elle peut être intégrée au processeur, bien que ce soit un cas assez rare.
Le cas le plus simple à étudier est celui d'une pile d'appel ayant sa propre mémoire, séparée de la mémoire pour les opérandes. La pile d'appel mémorise alors uniquement les adresses de retour pour les fonctions. Les deux piles n'ont pas forcément la même taille, à savoir que la pile d'appel est souvent plus petite que la pile des opérandes. Les appels de fonction, même si nombreux, utilisent assez peu de mémoire pour les adresses de retour. Vu qu'il y a deux piles, il y a deux pointeurs de pile. Un pointeur de pile pour la pile d'appel, un autre pour la pile des opérandes. Et vu qu'elles n'ont pas la même taille, les deux pointeurs n'ont pas forcément la même taille eux aussi. Ils sont gérés à par par le séquenceur.
La pile d'opérande : instructions et modes d'adressage
[modifier | modifier le wikicode]Les opérandes sont ajoutés ou retirés de la pile grâce à deux instructions nommées PUSH et POP.
- L'instruction PUSH permet d'empiler une donnée. Elle prend l'adresse de la donnée à empiler, charge la donnée, et met à jour le pointeur de pile.
- L'instruction POP dépile la donnée au sommet de la pile, la stocke à l'adresse indiquée dans l'instruction, et met à jour le pointeur de pile.


Un défaut lié à l'absence des registres est qu'il est impossible de réutiliser une donnée chargée dans la pile. Vu qu'une instruction dépile ses opérandes, on ne peut pas les réutiliser. Ceci dit, certaines instructions ont été inventées pour limiter la casse : on peut notamment citer l'instruction DUP, qui copie le sommet de la pile en deux exemplaires. On peut aussi citer l'instruction SWAP, qui échange deux données dans la pile. Mais ces instructions sont des opérations à faire en plus, comparé aux autres architectures. Les autres classes d'architectures n'ont pas à copier des données dans une pile, les empiler, et les déplacer avant de les manipuler.
![]() |
![]() |
Les instructions de calcul manipulent les opérandes qui sont au sommet de la pile. Les opérandes sont retirés de la pile par l'instruction et le résultat est placé au sommet de la pile. C'est du moins le cas sur les machines à pile dites "pures", mais d'autres architectures permettent de préciser la position des opérandes dans la pile. Une instruction peut indiquer qu'elle veut utiliser les opérandes situés 2, 3 ou 5 cases sous le sommet de la pile. Si les opérandes sélectionnés ne sont pas toujours retirés de la pile (tout dépend de l'architecture), le résultat est lui toujours placé au sommet de la pile. Ces jeux d'instruction n'utilisent pas la pile comme une mémoire LIFO, ce qui lui vaut le nom d'architecture à pseudo-pile.
Les machines à pile que je viens de décrire ne peuvent manipuler que des données sur la pile. Toutefois, des machines à pile plus évoluées ajoutent des instructions load-op, qui lisent la seconde opérande en mémoire RAM, avec une adresse mémoire ou un mode d'adressage pour les pointeurs. L'avantage est que la seconde opérande n'a pas à être placée sur la pile pour être utilisée. Mais de telles instructions sont assez peu utilisées.
Sur les machines à pile, l'adressage indirect utilise le sommet de la pile. L'adresse à lire est placée au sommet de la pile, elle est envoyée sur le bus d'adresse. Pour l'instruction STORE indirecte, la donnée à écrire est placée sous l'adresse, donnée et adresse sont dépilées par l'instruction STORE. Pour l'instruction LOAD indirecte, l'adresse à lire est dépilée au sommet de la pile, la donnée lue est empilée au sommet de la pile. Il est possible d'implémenter un adressage de type base + indice, il suffit de placer l'adresse de base et l'indice au sommet de la pile.
Les mémoires reliées à une machine à pile
[modifier | modifier le wikicode]L'implémentation d'une architecture à pile n'est pas la même si une seule pile est utilisée, ou si la pile d'appel est séparée de la pile d'opérande. Ajoutons que les machines à pile peuvent être de type Harvard ou Von Neumann. La pile d'appel contient des copies passées sur program counter, ce qui fait que la pile d'appel est alimentée par le séquenceur, directement.

Enfin, il arrive que les machines à pile utilisent deux RAM séparées : une pour la pile d'opérande et une pour les structures de données complexes, comme les tableaux. La première est une mémoire LIFO digne de ce nom, l'autre est une mémoire RAM tout ce qu'il y a de plus normale. La mémoire RAM pour les tableaux était accédée via des modes d'adressage spéciaux, les instructions de calcul pouvaient préciser l'adresse d'une donnée dans la RAM. Et les instructions LOAD et STORE pouvaient copier une donnée de la RAM vers la pile d'opérande ou inversement. Les architectures de ce type avaient souvent des registres d'indices pour faciliter les calculs d'adresse. Un exemple d'architecture de ce type était celui des mainframes Burroughs Large Systems.

Dans le meilleur des cas, une seule mémoire est utilisée pour les deux piles et le programme. Il y a des versions où la pile d'appel est intégrée dans le séquenceur, laissant la mémoire pour le programme et la pile d'opérande. Il faut dire que la pile d'appel ne contient que des adresses de retour, elle est assez petite. Assez pour être intégrée dans le processeur.
La microarchitecture d'une machine à pile
[modifier | modifier le wikicode]Dans ce qui va suivre, nous allons supposer que la pile d'appel est intégrée au séquenceur. Les architectures à pile de ce type peuvent être implémentées de plusieurs manières, la plus simple utilisant une architecture mémoire-mémoire améliorée, la plus complexe utilisant une architecture à registres sous-jacente.
La première implémentation utilise une architecture mémoire-mémoire sous-jacente, toute la difficulté de l'implémentation étant déportée dans le séquenceur. La pile d'opérande est placée en mémoire RAM, les instructions lisent leurs opérandes en RAM et empilent le résultat en RAM. Les instructions PUSH et POP font des copies en mémoire RAM pour empiler et dépiler les données. Pour cela, elles passent par l'intermédiaire d'un registre d’interfaçage, comme sur les architectures mémoire-mémoire.

Le séquenceur est adapté de manière à gérer une pile d'opérande, les instructions PUSH/POP/autres, et quelques autres détails. Le pointeur de pile est géré par le séquenceur, au même titre que le program counter. Il est incrémenté/décrémenté par l'unité de calcul d'adresse, qui est partagée avec le program counter.
Une telle implémentation intègre souvent un cache de pile, qui mémorise les données au sommet de la pile. Il s'agit réellement d'un cache, dans le sens où il contient une copie du sommet de la pile d'appel, qui est aussi en RAM. La pile est donc en RAM, totalement, et seules les portions utilisées de la pile sont maintenues dans le processeur. La gestion du débordement se fait en utilisant le remplacement des lignes de cache naturellement incorporé dans tout cache digne de ce nom.

Une autre implémentation ajoute un registre pour mémoriser l'opérande au sommet de la pile. Le registre agit comme un registre accumulateur. En effet, le sommet de la pile est le dernier résultat calculé par une instruction. De plus, il sera utilisé comme opérande de la prochaine opération. Du moins, tout cela est valable tant qu'on n'utilise pas d'instruction DUP ou SWAP, qui changent la position des opérandes dans la pile. L'implémentation n'est ni plus ni moins qu'une architecture à accumulateur au séquenceur modifié.

Une dernière implémentation place tout ou partie de la pile dans le processeur, pas seulement le sommet de la pile. Pour cela, elles utilisent une architecture registre-accumulateur améliorée, où le banc de registre est remplacé par une mémoire LIFO. Le sommet de la pile est stocké dans un registre accumulateur, le reste de la pile est dans une mémoire tampon de type LIFO, construite en améliorant un banc de registre. La mémoire LIFO ne permet de lire qu'un seul opérande, le second opérande situé sous le sommet de la pile.
- Il est techniquement possible d'implémenter la LIFO avec deux ports de lecture, ce qui fait que l'intérieur du processeur ressemble à une architecture LOAD-STORE avec des circuits en plus. Mais de telles implémentations sont rares.
Si jamais la LIFO est totalement remplie, le processeur peut gérer la situation de plusieurs manières. Avec la première, les données au bas de la pile débordent en mémoire RAM. Si la LIFO est déjà pleine au moment d'un PUSH, l'opérande au fond de la pile est alors envoyé en mémoire RAM. Reste qu'implémenter la gestion du débordement de la pile est quelque peu complexe. Une autre solution est de déléguer les débordements au logiciel. Un PUSH dans une pile pleine déclenche une exception matérielle, dont la routine gère la situation.

Exemple : la Harris RTX 2000
[modifier | modifier le wikicode]Une architecture à pile assez réputée est la NC4016. Elle avait comme particularité d'être connectée à trois mémoires : une pour la pile d'appel, une autre pour la pile d'opérande, une autre pour la mémoire programme (architecture Harvard). Son successeur est la Harris RTX 2000, une ancienne architecture à pile qui a eu un petit succès dans les cours d'architecture des ordinateurs.
Elle disposait de deux piles intégrées au processeur : une pile d'appel de 256 adresses de retour et une LIFO de 256 opérandes. Les deux piles n'avaient aucune protection en cas de débordement, à savoir pour vérifier si elles sont pleines ou non. La pile était donc intégrée dans le processeur, qui était en réalité un microcontrôleur. C’était une architectures Harvard, avec un bus relié à une mémoire pour le programme, et un G-bus pour les périphériques.

Sa microarchitecture est illustré ci-contre. Rassurez-vous, nous n’allons pas l'étudier en détail, pour une raison simple : le grand nombre d'interconnexions rend son étude très compliquée. Remarquons qu'il y a deux bus internes : un bus Y et un bus T. Le bus Y est connecté sur une entrée de l'ALU et on comprend facilement qu'il récupère la seconde opérande pour une instruction de calcul. Le bus T, quant à lui, prend en entrée le résultat et l'envoie à destination.
Les registres sont mine de rien assez nombreux. En premier lieu, on trouve le program counter noté PC et le registre d'instruction noté IR. Le sommet de la pile est mémorisé dans un registre dédié. Mais on trouve aussi des registres MD, SR et CR dédiés à des opérations spécifiques. Le registre MD est utilisé pour les multiplications, afin de mémoriser la seconde opérande. Le registre SR est utilisé pour le calcul de la racine carrée, car le processeur supporte cette opération dans son jeu d'instruction. Il y avait aussi un registre d'indice I.
Niveau circuits de calcul, il y avait une unité de calcul assez complexe, un circuit incrémenteur pour le porgram counter et un circuit décrémenteur pour gérer les pointeurs de pile.
Le support de plusieurs piles pour la multiprogrammation
[modifier | modifier le wikicode]Les ordinateurs Burroughs Large Systems étaient des machines à piles optimisées pour exécuter plusieurs programmes les uns à la suite des autres. C'est plus ou moins ce qui est fait sur les ordinateurs modernes, quand on veut exécuter plus de programmes qu'on a de coeurs. On change de programme régulièrement, chaque programme est interrompu pour laisser la place à un autre, et ainsi de suite. C'est le système d'exploitation qui se charge de changer d'un programme à un autre.
Pour cela, ils géraient nativement plusieurs piles, une par programme en cours d'exécution. Une pile fusionnait pile d'appel et pile d'opérande, elle servait les besoins pour un programme. De plus, ces processeurs avaient des instructions dédiées pour changer d'un programme à un autre.
Les architectures à pile de registres
[modifier | modifier le wikicode]Dans le chapitre précédent, nous avons parlé des architectures à accumulateur, et vu qu'il existait des architectures hybrides accumulateur-registres. Quelque chose d'équivalent existe pour les machines à pile. Il s'agit de processeurs qui ont plusieurs registres, mais les utilisent comme une pile d'opérande. En clair, de tels processeurs ont une pile d'opérande intégrée dans le processeur, de petite taille, typiquement 3 à 16 registres maximum. La pile de registres est gérée par le programmeur, qui a conscience qu'il y a une RAM séparée de la pile d'opérande intra-CPU.
L'extension flottante x87 des CPU x86
[modifier | modifier le wikicode]Un bon exemple est celui de l'extension x87 qui a ajouté les nombres flottants aux processeurs x86. Pour rappel, les processeurs x86 sont ceux qui sont présents à l'intérieur des PC, à savoir tous les processeurs Intel et AMD actuels. Il s'agit d'un jeu d'instruction qui a reçu de nombreux ajouts au cours du temps. Les ajouts en question sont appelés des extensions x86. Nous allons maintenant voir celle qui a ajouté la gestion des flottants au x86. L'extension x87 n'est plus utilisée depuis l'arrivée des CPU 64 bits, car elle a été remplacée par l'extension SSE.
Sur les tout premiers processeurs x86, le support des nombres flottants n'était pas implémenté. A la place, ils utilisaient des co-processeurs arithmétiques, appelés des coprocesseurs x87. Ils travaillaient en tandem avec un processeur x86 normal, et ne géraient que des instructions arithmétiques sur des flottants. Le premier coprocesseur de ce type était le 8087. Il a été suivi par le 187, le 287, le 387, le 487 et le 587. Ils étaient capables d'exécuter les 4 opérations de base (add, sub, mul, div), la racine carrée, des instructions de calcul de la valeur absolue (FABS) ou encore de changement de signe (FCHS), les opérations trigonométriques sinus, cosinus et tangente, l'arctangente, et des instructions de calcul de logarithmes ou d'exponentielles.
Les coprocesseurs arithmétiques x87 avaient 8 registres flottants, qui étaient gérés avec une pseudo-pile, ainsi que 3 registres de contrôle. Les 8 registres x87 sont ordonnés et numérotés de 0 à 7. Les registres x87 sont organisés sous la forme d'une pile de registres, similaire à une pile d'assiette, que l'on remplit dans un ordre bien précis. Lors du chargement d'une opérande de la RAM vers les registres, il n'est pas possible de décider du registre de destination. Si on veut ajouter des flottants dans nos registres, on doit les « remplir » dans un ordre de remplissage imposé : on remplit d'abord le registre 7, puis le 6, puis le 5, et ainsi de suite jusqu'au registre 0. Si on veut ajouter un flottant dans cette pile de registres, celui-ci sera stocké dans le premier registre vide dans l'ordre de remplissage indiqué au-dessus.
Prenons un exemple, les 3 premiers registres sont occupés par un flottant et on veut charger un flottant supplémentaire : le 4e registre sera utilisé pour stocker ce flottant. La même chose existe pour le « déremplissage » des registres. Imaginez que vous souhaitez déplacer le contenu d'un registre dans la mémoire RAM et effacer complètement son contenu. On ne peut pas choisir n'importe quel registre pour faire cela : on est obligé de prendre le registre non vide ayant le numéro le plus grand.
Les instructions à une opérande dépilent le flottant au sommet de la pile. Les instructions dyadiques peuvent dépiler les deux opérandes au sommet de la pile, mais elles peuvent aussi utiliser d'autres modes d'adressage. Elles peuvent aller chercher la seconde opérande en RAM, en fournissant une adresse. Mais elles peuvent aussi adresser n'importe quel autre registre de la pile en fournissant son numéro de registre. Avec ce dernier mode d'adressage, le processeur agit comme une sorte de processeur à accumulateur, avec le sommet de la pile servant d'accumulateur.
Pour charger des opérandes dans la pile d'opérande, l'extension x87 fournit trois instructions d'accès mémoire.
| Instruction | Description |
|---|---|
| FLD | Charge un nombre flottant depuis la mémoire vers notre pile de registres vue au-dessus. Cette instruction peut charger un flottant codé sur 32 bits, 64 bits ou 80 bits |
| FSTP | Déplace le contenu d'un registre vers la mémoire. Une autre instruction existe qui est capable de copier le contenu d'un registre vers la mémoire sans effacer le contenu du registre : c'est l'instruction FST |
| FXCH | Échange le contenu du dernier registre non vide dans l'ordre de remplissage (celui situé au sommet de la pile) avec un autre registre |
En somme, les registres de la pile sont adressables, du moins pour ce qui est de gérer la seconde opérande. C'est cette particularité qui vaut le nom de pseudo-pile à cette organisation à mi-chemin entre une pile et une architecture à registres.
Pour gérer la pseudo-pile, les registres pour les flottants sont associés à un registre d'état nommé Tag Word. Le registre Tag Word indique, pour chaque registre flottant, s'il est vide ou non. Avouez que c'est pratique pour gérer la pile de registres vue au-dessus ! Le registre tag word fait 16 bits, ce qui fait 2 bits pour chacun des 8 registres. Ces deux bits contiennent des informations sur le contenu du registre de données réservé.
- Si ces deux bits valent 00, le registre contient un flottant « normal » différent de zéro ;
- Si ces deux bits valent 01, le registre contient une valeur nulle : 0 ;
- Si ces deux bits valent 10, le registre contient un NAN, un infini, ou un dénormal ;
- Si ces deux bits valent 11, le registre est vide et ne contient pas de nombre flottant.
Le processeur x87 contient aussi deux registres d'état, nommés Control Word et Status Word. Le registre Status Word contient quelques bits, certains utilisés pour gérer la pseudo-pile, d'autres non. Il fait lui aussi 16 bits et c'est un registre d'état qui est utilisé pour qu'un programme puisse comprendre la cause d'une exception. Il contient le numéro du registre juste au-dessus du sommet de la pile, le numéro du premier registre vide dans l'ordre de remplissage. Mais il contient surtout des bits mis à 1 en cas de débordement de flottant, de division par zéro, lorsqu'un calcul a pour résultat un dénormal, etc. Le registre Control Word fait 16 bits et configure la gestion des arrondis.
Les avantages de l'absence de registres
[modifier | modifier le wikicode]Les architectures mémoire-mémoire et à pile se distinguent des autres sur un point : l'absence de registres. En comparaison, les architectures à accumulateur ont un registres, celles à registres généraux en ont plusieurs. La présence de registres réduit grandement le nombre d'accès mémoire par instruction, ce qui améliore grandement la performance. Au début de l'informatique, mémoire et processeur étaient tout aussi rapides, ce qui fait que les architectures à registre n'avaient pas d'avantage évident en termes de performances. Mais depuis, l’absence de registres est devenu un désavantage global.
| Classe d'architecture | Nombre d'accès mémoire par opération dyadique |
|---|---|
| Architecture à pile | Trois accès mémoire par opération : un par opérande, un pour le résultat |
| Architecture mémoire-mémoire | |
| Architecture à accumulateur | Un accès mémoire par instruction, pour lire la seconde opérande |
| Architecture à registres | Zéro si les opérandes sont dans les registres, un pour les opérations load-op, LOAD et STORE. |
L'absence de registre a cependant des avantages, voyons lesquels.
La rapidité des interruptions/appels de fonction
[modifier | modifier le wikicode]L'absence de registre a des avantages pour la gestion des procédures. Pas besoin de sauvegarder les registres du processeur à chaque appel de fonction, ni de les restaurer à la fin. La gestion de la pile est ainsi grandement simplifiée : pas de sauvegarde/restauration des registres signifie pas besoin d'instructions pour échanger des données entre registres et cadres de pile. Les programmes avec beaucoup d'appels de fonction économisent ainsi beaucoup d’instructions, ils étaient plus petits. L'avantage est d'autant plus important que les procédures/fonctions sont petites (une large partie des instructions est alors dédiée à la sauvegarde/restauration des registres) et nombreuses.
En conséquence, les architectures mémoire-mémoire et les architectures à pile ont un avantage pour ce qui est des appels de fonction. L'avantage concerne aussi les interruptions, qui sont des appels de fonction comme les autres. Ce qui fait que les architectures sans registres de données sont parfois utilisés comme processeurs pour gérer les entrées-sorties, vu que ces processeurs doivent fréquemment gérer des interruptions matérielles. Ils sont aussi très adaptés pour des processeurs faible performance dans l'embarqué, dans des cas d'utilisation où les interruptions matérielles sont fréquentes.
Les architectures à accumulateur sont aussi dans ce cas. Avec elles, il n'y a qu'un seul registre accumulateur en plus comparé aux architectures sans registres, ce qui ce qui rend la sauvegarde/restauration des registres très rapide. Et encore, dans quelques situations, l'accumulateur n'a pas à être sauvegardé. Et tout cela vaut aussi pour les interruptions. Aussi, beaucoup de processeurs embarqués à faible performance, qui doivent gérer un grand nombre d'entrées-sorties, sont des architectures à accumulateur. Leur simplicité de conception, leur facilité de programmation, leur parfaite adaptation aux interruptions fréquentes, les rend idéaux pour ce genre d'applications.
La densité de code
[modifier | modifier le wikicode]Un autre avantage des architectures à pile est la densité de code, à savoir la taille des programmes. Au début de l'informatique, la mémoire avait des performances similaires à celles du processeur, mais elle était de petite taille. Aussi il était avantageux d'avoir des programmes assez petits. Les architectures se distinguaient sur la densité de code. La taille d'un programme dépend de deux choses : le nombre d'instructions et la taille de celles-ci. Les architectures à pile se distinguent par des instructions très courtes, qui prennent peu de place en mémoire.
La taille des instructions est avant tout une question d'encodage des instructions. Pour rappel, une instruction contient un opcode qui encode l'opération, éventuellement un champ pour préciser le ou les modes d'adressage, et des références qui pointent vers l'opérande. La référence est soit une adresse mémoire, soit un nom, de registre. Et cela nous amène à la distinction entre architectures 0, 1, 2 et 3 adresses. Notons que ce qui nous intéresse ici est le cas des instructions dyadiques uniquement, car les autres instructions ont un encodage similaire d'une architecture à l'autre.
Les architectures à pile n'ont pas besoin de préciser de références, car les opérandes sont adressées implicitement. Et cela vaut pour les instructions dyadiques comme monadiques. En conséquence, elles sont aussi appelées architectures à zéro adresse par abus de langage. Les instructions se limitent généralement à un opcode, sauf pour les instructions de calcul et les branchements. Quelques architectures à pile ont des instructions annexes qui précisent l'adresse mémoire du second opérande, mais elles sont assez peu utilisées, la majorité des instructions adressent toutes les opérandes au sommet de la pile.
Les autres architectures ont besoin d'encoder des références, ce qui fait que leurs instructions sont beaucoup plus longues. Sur les architectures à accumulateur, les instructions se résument à un opcode couplé à une adresse mémoire, vu que l'accumulateur est adressé implicitement. Les architectures à registre doivent encoder deux à trois références, en plus de l'opcode. Par contre, les références sont généralement des noms de registre, ce qui fait que leurs instructions sont plus courtes que pour une architecture à accumulateur. Mais dans les deux cas, les instructions sont plus longue que sur une machine à pile.
- Les architectures mémoire-mémoire encodent deux/trois adresses mémoires en plus de l'opcode. Elles ont les instructions les plus longues.
Les développements précédents nous parlent de la taille des instructions, mais il faut aussi tenir compte du nombre d'instructions d'un programme. Pour rappel, ce nombre est appelée l'instruction path length et il est influencé par le jeu d'instruction du processeur. Et à ce petit jeu, les machines à pile ont un nombre d'instructions par programme plus élevé que sur les autres architectures, en grande partie à cause des instructions POP et PUSH. On doit PUSH les opérandes sur la pile avant de les utiliser, là où les autres opérandes peuvent les lire directement, via adressage explicite.
Par contre, ce désavantage est sur-compensé par les instructions plus courtes. Si on compare les différents types d'architectures, on s’aperçoit que c'est surtout la taille des instructions qui compte en premier lieu, suivi par le nombre d'instruction. Les machines à pile ont la meilleure densité de code, suivies par les architectures à registre, puis par les architectures à accumulateur, et enfin par les architectures mémoire-mémoire.
- Les architectures à registre ont un désavantage théorique lié à la gestion des procédures. Elles doivent sauvegarder les registres lors d'un appel de fonction et les restaurer quand la fonction termine. Cela implique des échanges avec la mémoire réalisée avec des instructions LOAD-STORE ou PUSH/POP. Plus les fonctions sont fréquentes et petites, plus le désavantage est important. Mais elles ont malgré un instruction path length plus favorable que pour les architectures à pile. Les architectures à accumulateur doivent aussi sauvegarder l'accumulateur lors des appels de fonction, et le restaurer après. Mais le désavantage est vraiment mineur, car elles ont moins de registres à sauvegarder.



