Fonctionnement d'un ordinateur/L'implémentation matérielle des branchements
L'implémentation des branchements implique tout le séquenceur, l'unité de chargement, et même les unités de calcul. L'implémentation des branchements demande que l'on puisse identifier les branchements, et altérer le program counter quand un branchement l'impose. L'altération du program counter est le fait de l'unité de chargement. Elle a juste besoin qu'on lui précise à quelle adresse brancher, et quand un branchement a lieu. Quant au séquenceur, il doit gérer tout le reste. Pour les branchements conditionnels, il faut aussi faire une comparaison dans l'ALU et envoyer son résultat au séquenceur.
Vu que les branchements impliquent tout le processeur, chemin de données inclu, nous ne pouvons pas en parler dans le chapitre sur l'unité de chargement, ni sur l'unité de contrôle, ni sur le chemin de données. Il est préférable de voir ceux-ci dans un chapitre à part, séparé des trois précédents. C'est pour cela que nous avons séparé ce chapitre des autres chapitres sur la microarchitecture d'un processeur. Nous allons y aborder les branchements, conditionnels comme inconditionnels, mais aussi les instructions à prédicats et les instructions skip. Elles ont des implémentations assez différentes, mais qui ont la même particularité : elles impliquent tout le processeur.
L'implémentation des branchements conditionnels
[modifier | modifier le wikicode]Les branchements inconditionnels sont les plus simples à gérer. Il suffit de détecter si une instruction est un branchement inconditionnel, et de déterminer où se trouve l'adresse de destination. Pour cela, on doit ajouter un circuit de détection des branchements, qui détecte si l'instruction exécutée est un branchement ou non. Il est situé dans le décodeur d'instruction. La détermination de l'adresse dépend du mode d'adressage et implique de configurer correctement le chemin de données. Il y a peu çà dire
Par contre, les branchements conditionnels demandent en plus de vérifier qu'une condition est respectée, ils demandent de faire calculer une condition pour savoir s'il faut faire le saut. Sur les jeux d'instruction modernes, tout est fait en une seule instruction : le branchement calcule la condition en plus de faire le saut. Mais les jeux d'instruction anciens séparaient le calcul de la condition et le branchement dans deux instructions séparées, ce qui demande d'ajouter un registre pour faire le lien entre les deux. L'instruction de test doit fournir un résultat, qui est mémorisé dans un registre adéquat. Puis, le branchement lit ce registre, et décide de sauter ou non. Pour rappel, il existe trois types de branchements conditionnels :
- Ceux qui doivent être précédés d'une instruction de test ou de comparaison.
- Ceux qui effectuent le test et le branchement en une seule instruction machine.
- Ceux où les branchements conditionnels sont émulés par une skip instruction, une instruction de test spéciale qui permet de zapper l'instruction suivante si la condition testée est fausse, suivie par un branchement inconditionnel.

Formellement, un branchement conditionnel demande de faire deux choses : calculer une condition, puis faire le branchement suivant le résultat de la condition. Dans ce qui suit, nous allons d'abord voir le cas où calcul de la condition et saut conditionnels sont réalisés tous deux par une seule instruction. Puis, nous verrons ensuite le cas où test et saut sont séparés dans deux instructions séparées. La raison est que le premier cas est le plus simple à implémenter. Le second cas demande d'ajouter des registres et quelques circuits, ce qui rend le tout plus compliqué.
Les circuits de saut conditionnel et de calcul de la condition
[modifier | modifier le wikicode]Le calcul de la condition adéquate est réalisé par un circuit assez simple, qui est partagé entre le séquenceur et le chemin de données.
Premièrement, deux opérandes sont lus depuis les registres, puis sont envoyés à un circuit soustracteur qui soustrait les deux opérandes. Le résultat de la soustraction n'est pas mémorisé dans les registres, mais quelques portes logiques extraient des informations importantes de ce résultat. Notamment, elles vérifient si : le résultat est nul, le résultat est positif/négatif, si la soustraction a entrainé un débordement entier signé, ou un débordement non-signé (une retenue sortante). Ces quatre résultats sont appelés les bits intermédiaires, et ils sont combinés pour calculer les différentes conditions.
En combinant les quatre résultats, on peut déterminer toutes les conditions possibles : si les deux opérandes sont égaux, si la première est inférieure/supérieure à la seconde, etc. Toutes les conditions sont calculées en parallèle et la bonne est alors choisie par un multiplexeur commandé par le séquenceur. Au passage, nous avions déjà vu ce circuit dans le chapitre sur les comparateurs, dans la section sur les comparateurs-soustracteurs.

Outre le calcul de la condition, un branchement conditionnel saute ou non à une certaine adresse. On sait déjà que le saut s'effectue en présentant l'adresse de destination sur l'entrée adéquate du program counter et en mettant à 1 son entrée de réinitialisation. La seule difficulté est de décider s'il faut mettre à jour le program counter ou non.
Le program counter doit être réinitialisé dans deux cas : soit on a un branchement inconditionnel, soit on a un branchement conditionnel ET que la condition est respectée. Détecter si la condition est respectée est assez simple : elle est dans un registre à prédicat, ou calculé à partir du registre d'état, comme vu plus haut. Reste à identifier les branchements et leur type. Pour cela, le séquenceur dispose de circuits qui détectent si l'instruction chargée est un branchement conditionnel ou inconditionnel. Ces circuits fournissent deux bits : un bit qui indique si l’instruction est un branchement conditionnel ou non, et un bit qui indique si l’instruction est un branchement inconditionnel ou non. Il reste alors à combiner ces deux bits avec le résultat de la condition, ce qui se fait avec quelques portes logiques. Le circuit final est le suivant.

Effectuer un branchement demande donc de combiner les deux circuits précédents, en mettant le second à la suite du premier. Le schéma ci-dessous montre ce qui se passe quand test et saut sont fusionnés en une seule instruction, où il n'y a pas de séparation entre instruction de test et branchement. Le circuit ci-dessous est le plus simple.

Avec une séparation entre test et branchement, les choses sont plus compliquées, car l'ajout de registres à prédicats ou d'un registre d'état complexifie le circuit. Et c'est ce que nous allons voir dans la section suivante.
Le registre d'état ou les registres à prédicats et les circuits associés
[modifier | modifier le wikicode]Voyons maintenant ce qui se passe quand on sépare le branchement en deux, avec une instruction de test séparée des branchements conditionnels. La répartition des tâches entre instruction de test et branchement conditionnel est assez variable suivant le processeur. Pour rappel, on peut faire de deux manières.
- La première est la plus évidente : l'instruction de test calcule la condition, le branchement fait ou non le saut dans le programme suivant le résultat de la condition. Le résultat des instructions de test est mémorisé dans des registres de 1 bit, appelés les registres de prédicat.
- La seconde méthode procède autrement. Les quatre bits tirés de l'analyse du résultat de la soustraction sont mémorisés dans le registre d'état. Le contenu du registre d'état est ensuite utilisé pour calculer la condition voulue par le branchement.
Dans les deux cas, il faut modifier l'organisation précédente pour rajouter les registres et quelques circuits annexes. Il faut notamment ajouter les registres eux-mêmes, mais aussi de quoi gérer leur adressage ou les contrôler. Dans les deux cas, les branchements lisent le contenu de ces registres, et décident alors s'il faut sauter ou non. Dans les deux cas, la soustraction des deux opérandes est réalisée dans le chemin de données, pareil pour la génération des quatre bits intermédiaires. Mais pour le reste, l'organisation change.
Le cas le plus simple est clairement celui où on utilise un registre d'état. La seule différence notable avec l'organisation précédente est que l'on ajoute un registre d'état. Mais les autres circuits sont laissés tels quels. La répartition des circuits est aussi modifiée : le calcul des conditions et le multiplexeur sont déplacés dans l'unité de chargement ou dans le séquenceur, alors qu'ils étaient avant dans l'unité de calcul.

L'autre cas est celui où les résultats des conditions sont mémorisés dans des registres à prédicats, connectés au séquenceur. Cela amène deux problèmes : l'instruction de test doit enregistrer le résultat dans le bon registre à prédicat, et il faut aussi lire le bon registre à prédicat suivant le branchement. Il faut donc gérer la sélection en lecture et en écriture. Rappelons que les registres à prédicats sont numérotés, ils ont un nom de registre dédié qui est fourni par le séquenceur. La sélection en lecture et écriture des registres à prédicat se base donc sur ces noms de registre. Pour la sélection en lecture, le choix du registre à prédicat voulu est réalisé par un multiplexeur, commandé par le séquenceur. Le multiplexeur est intégré à l'unité de chargement ou au séquenceur, peu importe. Pour l'enregistrement dans le bon registre à prédicat, le choix est réalisé en sortie de l'unité de calcul, généralement par un démultiplexeur.

L'implémentation des skip instructions
[modifier | modifier le wikicode]Passons maintenant au cas des skip instruction, qui permettent d'émuler les branchements conditionnels par une instruction de test spéciale. Pour rappel, une skip instruction permet de zapper l'instruction suivante si la condition testée est fausse, suivie par un branchement inconditionnel. . Dans ce cas, le program counter est incrémenté normalement si la condition n'est pas respectée, mais il est incrémenté deux fois si elle l'est. Les branchements inconditionnels s’exécutent normalement. Là encore, suivant la condition testée, on trouve un multiplexeur pour choisir le bon résultat de condition.

L'implémentation des instructions à prédicats
[modifier | modifier le wikicode]Les instructions à prédicats sont des instructions qui s’exécutent seulement si une condition précise est remplie. Elles sont précédées d'une instruction de test qui met à jour le registre d'état ou un registre à prédicat. L'instruction à prédicat récupère alors le résultat de la condition, calculé par l'instruction de test précédente, et l'utilise pour savoir si elle doit se comporter comme un NOP ou si elle doit faire une opération. Leur implémentation est variable et deux grandes méthodes sont possibles. La première n’exécute pas l'instruction si la condition est invalide, l'autre l’exécute en avance mais n'enregistre pas son résultat dans les registres si la condition se révèle ultérieurement invalide.
La première méthode exécute l'opération, mais l'annule si la condition n'est pas respectée. Le calcul des conditions est fait en parallèle de l'autre opération et l'annulation se fait simplement en n'enregistrant pas le résultat de l’opération dans les registres. Le calcul de la condition s'effectue dans le séquenceur, mais le résultat est envoyé dans le chemin de données pour configurer un circuit qui autorise ou non l'enregistrement du résultat dans les registres.
Un défaut de cette technique est que l'instruction est effectivement exécutée, ce qui fait que le processeur a consommé un peu d'énergie et a pris un peu de temps pour faire le calcul. L'autre conséquence est que l'instruction mobilise une unité de calcul ou de transfert entre registre, le banc de registres, etc. En soi, ce n'est pas un problème. Mais ça l'est sur les processeurs modernes, qui sont capables d’exécuter plusieurs instructions en parallèle, dans un ordre différent de celui imposé par le programmeur. Nous verrons ces techniques d’exécution en parallèle dans les derniers chapitres du cours. Toujours est-il que sur ces processeurs, une instruction à prédicats va mobiliser des ressources matérielles comme l'ALU ou le bus interne, pour éventuellement fournir un résultat inutile, alors qu'une autre instruction aura pu prendre sa place et calculer des données utiles.

La seconde méthode est la plus intuitive : elle consiste à lire le registre d'état/de prédicat, pour décider s'il faut faire ou non l'opération. Pour cela, le séquenceur lit le registre d'état/à prédicat, et génère les signaux de commande adaptés : il génère les signaux de commande d'un NOP si la condition n'est pas respectée, et il génère les signaux de commande pour l'opération voulue sinon. L’avantage de cette méthode est que l'instruction ne s’exécute pas si la condition n'est pas remplie. Le processeur ne gâche pas d'énergie pour rien, il peut immédiatement passer à l'instruction suivante si celle-ci est disponible, etc. De plus, sur les processeurs modernes capables d’exécuter plusieurs instructions en parallèle, on ne mobilise pas de ressources matérielles si la condition n'est pas remplie et celles-ci sont disponibles pour d'autres instructions.
