CSC5101 – Advanced Programming of Multicore Architectures

Portail informatique

Du transistor au processeur

Ce Cours/TP a pour objectif de vous faire comprendre comment est conçu un processeur à partir de transistors.

Dans ce cours, nous supposons que nous sommes échoués sur une île déserte parfaitement inintéressante et sans ordinateur. Comme nous avons beaucoup de courage, une envie folle de jouer à pacman, un vieux générateur électrique et une énorme boîte pleine de transistors et de fils électriques, nous allons donc construire un petit ordinateur pour passer le temps.

Il est probable que de terminer ce TP vous demande environ 4 à 5h de travail supplémentaire en hors-présentiel. Les exercices 1 à 12 sont obligatoires, mais la suite est optionnelle. Toutefois, si vous vous amusez autant que vos vos enseignants lorsqu'ils ont conçu ce TP, allez jusqu'au bout est relativement plaisant puisque vous aurez réussi à concevoir un processeur 8 bits complet relativement avancé.

Le transistor

Nos transistor sont de type MOS (Metal-Oxide-Semiconductor), ceux les plus couramment utilisés pour construire les micro-processeurs. Il existe deux types de transistors MOS : les transistors nMOS et les transistors pMOS. Dans les processeurs actuels, on trouve les deux types de transistors, on parle donc de technologie CMOS (Complementary Metal-Oxide-Semiconductor).

Transistors nMOS et cMOS.

Comme présenté sur la figure , un transistor de type MOS possède trois connexions :
  • la source, nommée S,
  • la grille, nommé G.
  • le drain, nommé D,

Un transistor MOS se comporte comme un interrupteur dans lequel le bouton poussoir peut être activé avec de l'électricité. Pour un nMOS, lorsque la grille est alimentée, la source est connectée au drain, alors que pour un transistor pMOS, c'est l'inverse. Pour illustrer ce fonctionnement, si on imagine une rivière coulant de la source vers le drain, la grille agit comme une sorte de barrage empêchant l'eau de passer. Dans le cas d'un nMOS, lorsque la grille est alimentée, c'est comme si le barrage était ouvert : le courant passe de la source au drain. Dans le cas d'un pMOS, c'est l'inverse : lorsque la grille est alimentée, le barrage est fermé et le courant ne peut pas passer.

Du transistor à la porte NAND

Les transistors permettent de construire des portes logiques qui sont à la base de l'électronique. Dans cet question, nous étudions notre première porte logique : la porte NAND (pour Not AND). L'avantage de la porte NAND, c'est que c'est une porte logique universelle : elle permet de construire des portes logiques NOT, OR, AND ou XOR comme vous le verrez dans l'exercice suivant.

Image not found      
Porte NAND
Image not found
A B Out
0 0 1
0 1 1
1 0 1
1 1 0
.aLa porte logique NAND (credit wikipedia).
.bTable de vérité de la porte logique NAND.

La construction de la porte logique NAND à partir de portes nMOS et pMOS est donnée en figure .a, alors que la table de vérité de cette porte est donnée en figure .b. Dans la figure .a, Vdd est le courant haut qui correspond, par convention, à la valeur vrai. Vss est le courant bas qui correspond, par convention, à la valeur faux. Les deux transistors du haut (avec un petit rond sur la grille) sont des pMOS qui empêchent le courant de passer entre la source et le drain lorsque la grille est alimentée, alors que les deux transistors du bas (sans petit rond sur la grille) sont des nMOS qui laissent donc le courant passer entre la source et le drain lorsque la grille est alimentée.

Vérifiez que le circuit présenté en figure .a met bien en œuvre une porte NAND.

Lorsque A vaut 0 (faux), le transistor du bas de la figure associé à A empêche le courant de passer entre la source et le drain, donc Out n'est pas connecté à Vss (0). Ensuite, dans cette configuration, le transistor du haut associé à A laisse le courant passer entre Vdd (1) et Out : Out vaut donc 1. Exactement pour la même raison, si B vaut 0, Out vaut 1.

Enfin, lorsque A et B valent 1, les deux transistors du haut de la figure empêchent le courant de passer, ce qui déconnecte Out de Vdd (1). Dans ce cas, les deux transistors du bas de la figure laissent passer le courant de Vss (0) à Out : Out vaut donc 0.

Prise en main du simulateur de circuit

Maintenant que vous avons notre première porte logique, nous pouvons l'utiliser pour construire les autres portes logiques. Avant de nous lancer dans cette mise en œuvre, nous prenons en main l'environnement de développement que nous utiliserons pour créer des circuits.

Connexion

Dans un navigateur Web, connectez-vous au site https://circuitverse.org/simulator. Ce site permet de construire des circuits logiques de façon conviviale. De façon à pouvoir enregistrer vos travaux, il faut créer un compte. Pour créer un compte, cliquez sur Sign In. Ensuite, cliquez sur New user? Sign up. À partir de ce point, vous pouvez soit créer un nouveau compte, soit, si vous en avez un, utiliser votre compte google ou votre compte facebook pour vous authentifier.

Configuration d'un projet

Maintenant que vous avez un compte, vous pouvez entrer dans le simulator. Cliquez sur le menu Simulator qui se trouve en haut de la fenêtre. Vous pouvez commencer à configurer votre projet. Donnez lui un nom, par exemple CSC4536, en modifiant la partie Project Properties.

Les entrées

Avant d'aller plus loin, nous allons placer un premier élément dans notre circuit. Allez dans l'onglet Input se trouvant à gauche de la fenêtre et sélectionnez la première icône (lorsque vous passez dessus avec la souris, vous devriez voir Input). Déplacez votre souris sur l'espace de travail (la grille se trouvant à droite de la fenêtre), et cliquez quelque part. Félicitations, vous avez construit une entrée binaire ! Vous devriez constatez que, lorsque vous cliquez sur votre entrée binaire, vous pouvez la faire passer de 0 à 1 et inversement.

Enregistrement

Avant d'aller plus loin, nous allons vérifier que nous pouvons bien sauver notre travail. Dans le menu Project, cliquez sur Save Online, ce qui va créer une sauvegarde de votre projet hébergée sur le serveur (vous pouvez aussi enregistrer votre travail hors-ligne avec Save Offline). Pour vérifier que tout s'est bien passé, allez dans la configuration de votre compte représenté par votre nom d'utilisateur en haut à droite de la fenêtre, et cliquez sur Dashboard. Vérifiez que vous retrouver bien votre projet et cliquez sur Launch pour relancer le simulateur.

Déplacer un élément

Avant de se lancer dans des conceptions plus intéressantes, nous allons juste apprendre à créer un circuit simple constitué d'une unique porte logique NAND. Vous trouverez cet élément dans l'onglet Gates à gauche de la fenêtre. Dans un premier temps, ajoutez une seconde entrée et une porte logique NAND. Vous devriez remarquer que, lorsque vous passez sur un élément dans l'onglet Gates, son nom s'affiche. Vous devriez aussi remarquer que, lorsque vous cliquez sur un élément placé dans l'espace de travail, vous pouvez le déplacer.

Pour déplacer un élément, il faut cliquer à l'intérieur de l'élément et non sur les petits points verts se trouvant sur le bord et représentant les points de connexions.

Constructions de fils (1/2)

Nous allons maintenant apprendre à connecter les deux entrées à la porte logique. Pour connecter deux éléments, il faut créer un fil électrique entre ces éléments. Les points de connexions sont représentés par les points verts se trouvant sur le bord d'un élément. Lorsque vous cliquez sur un point de connexion et que vous déplacez votre souris, vous commencez à créer un fil. Connectez les entrées à la porte NAND.

Sorties

Pour finir, nous allons créer une sortie permettant de visualiser le résultat du calcul effectué par notre porte NAND. Dans l'onglet Output à gauche de l'écran, sélectionnez le premier élément (nommé Output). Cet élément, que nous appellerons une sortie binaire, permet d'afficher l'état d'un fil électrique (0 ou 1). Ajoutez une sortie binaire et connectez la sortie de la porte NAND à cette sortie. Vous devriez obtenir le circuit présenté en figure . Vérifiez, en modifiant les entrées, que votre circuit respecte bien la table de vérité de la porte NAND. Vous devriez remarquer qu'un fil alimenté (valeur 1) apparaît en vert clair alors qu'un fil non alimenté (valeur 0) apparaît en vert foncé.

Un circuit NAND.

Constructions de fils (2/2)

Pour continuer cette prise en main de l'outil, nous allons apprendre à mettre un circuit en court circuit, c'est-à-dire que nous allons à la fois mettre les valeurs 0 et 1 sur un fil. Il ne faut bien sûr jamais mettre un circuit en court-circuit, cette question n'a que pour but de vous montrer ce qui se passe, ce qui va vous permettre d'identifier les erreurs dans vos circuits.

Pour réaliser ce court circuit, cliquer quelque part sur le fil reliant une des entrées à la porte NAND, ce qui permet de démarrer un second fil à partir de ce point. Construisiez un fil qui contourne entièrement la porte (par le bas si vous partez du bas ou par le haut sinon). Pour cela, il faut construire plusieurs fils. Vous devriez obtenir le résultat présenté en figure . Vous devriez voir des messages s'afficher en bas de la fenêtre vous indiquant qu'il y a un problème. Vous devriez aussi voir que le fil reliant la porte à la sortie est à moitié vert foncé et à moitié vert clair, et que le simulator vous a ajouté de petits rond aux points de connexions qui permettent de remonter à l'origine du problème (sur le haut de la figure dans le cas de la figure ).

Un circuit NAND en court-circuit.

Suppression d'éléments

Nous allons réparer notre circuit en détruisant le court-circuit. Pour cela, cliquez sur un fil et appuyez sur la touche backspace (retour arrière se trouvant en haut à droite de votre clavier), ce qui détruit un élément. Supprimez tous les fils réalisant le court-circuit.

Déplacer plusieurs éléments

Pour finir notre prise en main, nous apprenons à déplacer plusieurs éléments. Pour cela, il suffit d'effectuer une sélection en appuyant sur la touche Shift (remarquez que si vous n'appuyez pas sur la touche Shift alors que vous cliquez sur une zone sans éléments avant de déplacer la souris, vous déplacez simplement la grille). Ensuite, vous pouvez, en cliquant sur l'un des éléments sélectionnés , déplacer l'ensemble de ces éléments.

De la porte NAND aux autres portes logiques

Le but de cet exercice est de construire les portes logiques manquantes à partir de la porte logique NAND. Les tables de vérités et les icônes associées aux portes que vous devez construire sont présentées en figure .

Porte NOT
Image not found
a Out
0 1
1 0
Porte AND
Image not found
a b Out
0 0 0
0 1 0
1 0 0
1 1 1
Porte OR
Image not found
a b Out
0 0 0
0 1 1
1 0 1
1 1 1
Porte NOR
Image not found
a b Out
0 0 1
0 1 0
1 0 0
1 1 0
Porte XOR
Image not found
a b Out
0 0 0
0 1 1
1 0 1
1 1 0
Porte XNOR
Image not found
a b Out
0 0 1
0 1 0
1 0 0
1 1 1
Tables de vérités des principales portes.

Commencez par nettoyer votre espace de travail en supprimant le circuit créé à l'exercice précédent. Pour cela, sélectionnez tout votre circuit et utilisez backspace pour le supprimer (vous pouvez aussi aller dans le menu Project->Clear Project).

Ensuite, en considérant que vous n'avez que la porte logique NAND, mettez en œuvre les autres portes. Au fur et à mesure que vous construisez de nouvelles portes, vous pouvez bien sûr les utiliser pour construire les suivantes.

Nous vous conseillons de construire les portes dans cet ordre :

  • NOT Remarquez que NAND(A,A) = NOT(A).
  • AND Remarquez que NOT(NAND(A,B)) = AND(A,B).
  • OR Remarquez que OR(A,B) = NOT(AND(NOT(A),NOT(B))) = NAND(NOT(A),NOT(B)).
  • NOR Remarquez que NOR(A,B) = NOT(OR(A,B)).
  • XOR Remarquez que XOR(A,B) se comporte un peu comme OR(A,B), sauf quand A et B valent 1 : dans ce cas, on veut avoir 0 comme sortie. On peut donc facilement voir que XOR(A,B) = AND(A et B différent de 1, OR(A,B)). Ensuite une porte logique mettant en œuvre "A et B différent de 1 donne 1" est simplement la porte NAND. Donc, XOR(A,B) = AND(NAND(A,B),OR(A,B)).
  • XNOR Remarquez que XNOR(A,B) = NOT(XOR(A,B)).
Félicitations, vous avez maintenant construit les éléments de base permettant de concevoir des ordinateurs à partir de transistors !

Du bit à l'entier

Maintenant que nous avons nos portes logiques de base, nous pouvons commencer à concevoir des entiers. Cet exercice a pour but de vous présenter les outils fournis par le simulateur pour manipuler les entiers.

L'entrée 4 bits

À cette étape, nous apprenons à créer une entrée sur plusieurs bits. Commencez donc par ajouter une entrée. Si vous sélectionnez cette entrée, vous devriez voir apparaître ses propriétés. Donnez la valeur 4 à la propriété BitWidth de façon à ce que l'entrée soit une entrée avec 4 bits.

Affichage en hexadécimal

Le simulateur circuitverse permet d'afficher un nombre 4 bits facilement. Dans l'onglet Output, sélectionnez HexDisplay (5ième icône) et placez un HexDisplay à côté de votre entrée. Connectez votre entrée au HexDisplay. Vous pouvez maintenant voir apparaître la représentation hexadécimale de votre entrée. Même si dans la suite de l'exercice, nous ne vous demandons que rarement explicitement d'utiliser en HexDisplay, n'hésitez pas à en utiliser pour comprendre et debugger vos circuits.

Si vous n'êtes pas à l'aise avec la représentation des nombres en binaire et en hexadécimal, cliquez sur l'aide.

On commence par étudier les nombres sur 4 bits. Ce sont des nombres écrits en base 2 qui possèdent 4 digits. La table de correspondance de la figure vous présente la correspondance entre les 16 premiers nombres binaires, décimales et hexadécimales. L'hexadécimal est simplement une représentation des nombres en base 16 dans laquelle les nombres décimaux de 10 à 15 sont représentés par les lettres de A à F. Dans la table, les nombres suffixés par b sont des nombres représentés en binaire, alors que les nombres préfixés par 0x sont des nombres représentés en hexadécimal.

 Binaire   Décimal   Hexadécimal 
0000b 0 0x0
0001b 1 0x1
0010b 2 0x2
0011b 3 0x3
0100b 4 0x4
0101b 5 0x5
0110b 6 0x6
0111b 7 0x7
1000b 8 0x8
1001b 9 0x9
1010b 10 0xA
1011b 11 0xB
1100b 12 0xC
1101b 13 0xD
1110b 14 0xE
1111b 15 0xF
Correspondance binaire/décimale/hexadécimale.

On voit aussi qu'on peut représenter un nombre sur 8 bits avec deux digits hexadécimaux. Par exemple, 10010011b est le nombre 1001b * 1000b + 0011 = 0x9 * 0x10 + 0x3 = 0x93.

Le splitter

Maintenant que nous avons une entrée quatre bits, il faut être capable de la décomposer : en effet, actuellement, nous n'avons construit que des circuits capables de manipuler une entrée sur un bit. Pour cela, il faut utiliser un Splitter que vous trouverez dans l'onglet Misc. Lorsque vous créez un Splitter, il faut d'abord donner le nombre de bit total de votre Splitter, c'est-à-dire 8 dans notre cas. Ensuite, il faut indiquer la décomposition que l'on souhaite. Ici, nous voulons retrouver chacun des 4 bits, donc il faut saisir 1 1 1 1. Si par exemple vous aviez voulu décomposer votre nombre avec les 3 bits d'un côté puis le dernier ensuite, il aurait donc suffit de saisir 3 1.

Porte à entrées multiples et à nombre de bits variables

Dans la suite de l'exercice, nous aurons besoin de savoir si une entrée 4 bits est égales à 0. Nous allons donc concevoir ce circuit à cette question en partant de notre entrée 4 bits. Pour savoir si un ensemble de bits est égal à 0, il suffit de faire OR binaire avec chacune des entrées, puis d'inverser cette entrée avec une porte NOT. Pour notre entrée 4 bits, nous aurions donc besoin de 3 portes OR. Comme poser 3 portes OR et les connecter aux différentes entrées est bien fastidieux, circuitverse nous permet de construire des portes à entrées multiples, ce qui permet de ne poser qu'une unique porte. Ajoutez donc une porte OR. Ensuite, dans ses propriétés, mettez 4 comme Input Size. Vous avez maintenant 4 entrées à votre porte OU que vous pouvez connecter à votre Splitter.

Vous pouvez aussi remarquer que vous pouvez configurer le BitWith dans les propriétés, ce qui permet de faire des calculs bits à bits. Par exemple, un OR à 4 bits avec les nombres binaires 1100 et 0101 vous renverra 1101.

Le circuit z4?

Maintenant, pour savoir si notre entier sur 4 bits est égal à 0, il suffit d'ajouter une porte NOT 1 bit derrière notre porte OR. Plutôt que d'ajouter une porte NOT, nous vous conseillons de condenser votre circuit en remplaçant la porte OR à 4 entrées par une porte NOR à 4 entrées: cette parte calcul un OR sur les 4 entrées et inverse le résultat.

Après avoir remplacé votre porte OR par une porte NOR, ajoutez une sortie 1 bit pour visualiser le résultat. Comme ce sera utile pour la suite, nous allons en profiter nommer notre sortie. Dans le champ Label de l'onglet Porperties, donnez le nom z? à votre sortie. Vous devriez voir ce z? à droite de votre sortie.

Ne détruisez pas ce circuit capable de dire si un entier 4 bits est égal à 0 car nous allons en avoir besoin à plusieurs reprises dans les exercices suivants.

Réutilisation de circuits

Dans cet exercice, nous apprenons à réutiliser des circuits que nous avons déjà conçu.

Donner un nom à un circuit

Pour réutiliser un circuit, il faut commencer par le nommer. Dans l'onglet Properties du circuit que vous avez conçu à l'exercice précédent, vous pouvez changer le nom du circuit dans la zone Circuit:. Donnez le nom z4 à votre circuit.

Réutilisation d'un circuit

Nous allons maintenant créer un second circuit qui va réutiliser z4?. Dans le menu Circuit, sélectionnez New Circuit +. Vous pouvez aussi appuyer sur le bouton + dans la barre au dessus de votre circuit.

Pour réutiliser notre circuit z4? dans notre nouveau circuit, il suffit de retourner dans le menu Circuit, de sélectionner Insert SubCircuit, puis de sélectionner z4?. Vous pouvez voir que votre circuit s'appelle z4? et que sa sortie s'appelle z? puisque c'est le nom que nous lui avons donné à la question précédente.

Affichage d'un circuit

À cette question, nous apprenons à configurer l'afficher d'un circuit. Pour cela, il faut aller dans le circuit z4? et de cliquer sur Edit Layout dans l'onglet Properties.

Si vous avez le courage d'aller jusqu'au bout de la conception de notre CPU, vous vous rendrez compte qu'il est plus commode que le circuit z4 aille de la droite vers la gauche (et non de la gauche vers la droite comme c'est actuellement le cas) et qu'il soit le plus petit possible. Pour cela, dans la fenêtre d'édition du Layout, mettez la sortie z? à gauche et l'entrée non nommée à gauche. Ensuite, désactivé le Title Enable pour faire disparaître le nom de du circuit.

Dans la fenêtre de test (celle où vous avez réutilisé le circuit z4?, ajoutez une entrée 4 bits et une sortie 1 bit. Vu qu'on a inverser le sens de notre z4?, il est plus commode de placer les connecteurs des entrées et des sorties respectivement à droite et à gauche. Pour cela, cliquez sur l'entrée, et dans sa fenêtre propriété, mettez Orientation à LEFT. De façon similaire, inversez l'orientation de la sortie. Vous pouvez aussi nommer ces entrées et sorties et placer leur Label au dessus en modifiant Label Direction.

Une fois toute la configuration modifiée, vous devriez réussir à obtenir le circuit présenté en figure .

Réutilisation du circuit z4?.

L'additionneur

À cette question, nous réalisons notre premier circuit complexe&nbsp: un circuit capable d'additionner deux entiers. Nous nous cantonnons à un additionneur 4 bits pour comprendre le principe de construction car construire un additionneur 8 bits demande de tirer beaucoup plus de fil. Une fois que vous aurez compris le principe de construction, de façon à avoir un additionneur 8 bits, vous pourrez réutiliser celui qui est directement fourni par circuitverse.

Nettoyage

Avant d'aller plus loin, vous pouvez commencer par nettoyer votre projet. Dans la suite, vous aurez encore besoin du circuit z4? que vous avons conçu aux question précédentes. En revanche, les autres circuits ne nous servent plus à rien à vous pouvez tous les supprimer.

Le demi-additionneur 1 bit

Comme première étape, nous allons réaliser un circuit permettant d'additionner deux bits (un bit est une valeur binaire, pouvant donc prendre les valeurs 0 ou 1). Exactement comme on le fait en décimal, il peut il y avoir une retenue (carry en anglais), donc votre circuit doit produire un résultats sur deux bits :

  • 0 + 0 donne 0 et je retiens 0,
  • 0 + 1 donne 1 et je retiens 0,
  • 1 + 0 donne 1 et je retiens 0,
  • 1 + 1 donne 0 et je retiens 1 (comme 5 + 5 donne 0 et je retiens 1 en décimal),
a b s cout
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1
Table de vérité d'un demi-additionneur.

La figure vous donne la table de vérité de l'additionneur. Les entrées a et b sont les nombres à additionner alors que s est la somme et cout la retenue.

Commencez par créer un nouveau circuit que vous nommerez half-add. Ensuite, réalisez le circuit permettant d'effectuer une addition binaire. Pensez à nommer vos entrées (par exemple v0 et v1) et vos sorties (par exemple res pour le résultat et cout pour la retenue).

Vous pouvez remarquer que :

  • la somme sum réalise exactement un XOR entre a et b,
  • on a retenue si et seulement si a et b valent 1.

L'additionneur 1 bit

Comme on le fait en décimal, quand on effectue un calcul sur plusieurs digits, il faut non seulement additionner les deux digits courants, mais il faut aussi additionner ce résultat avec la retenue de l'étape précédente. Nous réalisons donc ce circuit à cette étape. Créez un nouveau circuit que vous nommerez 1-add (pour additionneur 1 bit).

Ajoutez 3 entrées nommées v0, v1 et cin (retenue d'entrée provenant de l'étape précédente). Ajoutez aussi 2 sorties nommées res pour la somme et cout pour la retenue de sortie.

Enfin, utilisez des circuits half-add (menu Circuit->Insert SubCircuit) pour que res soit égal à la somme de v0, v1 et cin, et que cout soit égal à la retenue de cette somme.

Pour la somme, il suffit d'enchaîner deux half-add. Pour la retenue, il suffit que l'une des deux additions génère une retenue pour que le 1-add génère une retenue. Le cout du circuit 1-add est donc égal à un OU entre les sorties cout des deux half-add

L'additionneur 4 bits

Vous pouvez maintenant créer l'additionneur 4 bits. Pour cela, vous avez besoin des entrées suivantes :

  • v0 sur 4 bits,
  • v1 sur 4 bits,
  • cin sur 1 bit, cette entrée donne la retenue initiale, qui est bien sûr égale à 0 pour une addition, mais vous verrez dans les questions suivantes comment exploiter cette entrée pour faire des soustractions.

Vous avez aussi besoin des sorties suivantes :

  • res le résultat de la somme sur 4 bits,
  • cout la retenue finale sur 1 bit.

De façon à vérifier que vos calculs sont corrects, pensez à connecter v0, v1 et res à des HexDisplay. Pensez aussi à utiliser des Splitters pour retrouver les valeurs de chacun des bits des entrées v0 et v1. Pour la sortie, il faut aussi utiliser un Splitter de 4 bits vers 1 1 1 1 bits, mais il faut l'orienter vers la gauche.

Avec 4 circuits 1-add, effectuez une addition sur 4 bits.

Pour les retenus, il faut les connecter en cascade. Le cin du premier 1-add reçoit la retenue d'entrée du circuit, et chaque cin suivant reçoit le cout de l'étape précédente.

Félicitations, vous venez de créer le composant électronique le plus "intelligent" d'un ordinateur !

L'unité de calcul arithmétique et logique (version simple)

Dans cet exercice, nous concevons un circuit nommé ALU permettant de réaliser des calculs arithmétiques et logiques. Vu le temps imparti, nous concevons notre ALU en deux étapes. Dans cet exercice, nous construisons une ALU uniquement capable d'effectuer des additions, mais suffisante pour faire fonctionner notre processeur. Cette ALU est particulièrement simple mais offre l'interface d'une ALU complète et complexe.

Si vous voulez réaliser une ALU plus complexe, n'hésitez pas, après avoir terminé le processeur, à faire l'exercice TODO qui vous montre comment ajouter des opérations logiques et des opérations de décalage de bits à votre ALU.

Une ALU simple

Créez un circuit nommé ALU. Ce circuit doit prendre en entrée :
  • Un entier v0 encodé sur 8 bits,
  • Un entier v1 encodé sur 8 bits,
  • Un entier func encodé sur 5 bits qui indiquera, à terme, l'opération qu'on souhaite effectuer avec v0 et v1.

Notre ALU initiale n'est que capable d'effectuer des additions et va donc ignorer l'entrée func. À terme, pour effectuer une addition, func doit valoir 00010.

En sortie, ALU doit renvoyer le résultat de l'opération (res sur 8 bits) et un drapeau permettant de savoir comment s'est passée l'opération (flag sur 8 bits). Dans le cas d'une addition et à cette étape, le bit 0 de flag doit stocker le bit de retenu (cout).

Comme notre additionneur ne fait que 4 bits, nous allons réutiliser l'additionneur fourni par circuitverse. Vous pouvez le trouver dans le l'onglet Circuit Element dans la catégorie Misc. C'est le quatrième circuit. Il prend en entrée (à gauche en partant du haut)&nbsp: A (v0 pour nous), B (v1 pour nous) et Cin (une entrée valant toujours 0). Il donne en sortie (à droite en partant du haut) : Sum (res pour nous) et Cout que nous utiliserons pour construire flag.

Pour la valeur initiale de Cin, vous avez besoin d'une valeur constante. Vous pouvez la trouver dans les Input sous le nom ConstantVal (5ième icône). Ici, une valeur constante de 0 nous convient, mais vous pourriez la changer en double cliquant sur le composant une fois ajouté au circuit.

Pour le drapeau, il faut utiliser un Splitter orienté gauche pour assembler la valeur de Cout avec une valeur constante 0 sur 7 bits. Pour construire cette valeur, il suffit d'ajouter une valeur constante et de changer son BitWidth. Le Splitter doit donc agréger une valeur sur 1 bit avec une valeur sur 7 bits&nbsp: il a une a taille de 8 et la décomposition est 1 7.

Le multiplexeur

Dans un circuit, il arrive souvent d'avoir besoin de sélectionner une entrée parmi N en fonction d'une valeur. Ce mécanisme peut par exemple être utilisé pour sélectionner les registres qui participent à une instruction. Dans cet exercice, nous mettons un œuvre des multiplexeurs dont le rôle est justement de sélectionner une entrée parmi N.

Dans cet exercice, nous apprenons à réaliser des multiplexeurs. Dans la suite du TP, nous réutiliserons le multiplexeur fourni par circuitverse et que pouvez trouver les décodeurs et plexeurs.

1-1-mux

On commence par construire un multiplexeur permettant de sélectionner une entrée parmi deux et dans lequel chaque entrée est un bit. Créez un nouveau circuit nommé 1-1-mux. Ce circuit doit posséder deux entrées binaires nommées x0 et x1, et un sélecteur nommé sel. Le circuit doit aussi posséder une sortie binaire nommée out. Quand sel vaut 0, le circuit doit renvoyer x0 sur out, et quand sel vaut 1, le circuit doit renvoyer x1 sur out.

Le circuit renvoie 1 si et seulement si :

  • x0 vaut vrai et sel vaut 0,
  • ou x1 vaut vrai et sel vaut 1.
La formule logique du circuit est OR(AND(x0, NOT(sel)), AND(x1, sel)).

1-2-mux

Souvent, on a besoin de sélectionner des entrées avec plus que 1 bit. À cette question, nous construisons le circuit 1-2-mux capable de sélectionner une entrée parmi deux entrées de deux bits. L'idée à cette question est que vous compreniez le principe de construction. Pour manipuler plus de bits, nous réutiliserons dans la suite le multiplexeur fourni par circuitverse.

En vous inspirant de l'additionneur 4 bits et en réutilisant le 1-1-mux, construisez un circuit 1-2-mux avec deux entrées de deux bits x0 et x1, et une entrée de 1 bit sel. Lorsque sel vaut 0, votre circuit doit renvoyer x0, sinon, il doit renvoyer x1.

2-2-mux

Parfois, il faut aussi être capable de sélectionner une entrée parmi 2nn est strictement plus grand que 1. À cette étape, nous construisons le circuit 2-2-mux capable de sélectionner une entrée parmi 22. Une fois que vous aurez compris le principe, vous pourrez réutiliser le multiplexeur fourni par circuitverse qui permet de spécifier une valeur quelconque pour n.

En réutilisant le 1-2-mux, construisez un circuit permettant de sélectionner une valeur parmi 4 dans lequel chaque valeur est encodée sur 2 bits.

Il faut utiliser un total de 3 1-2-mux. Les 2 premiers sont connectés directement aux entrées et le premier bit du sélecteur sert à sélectionner une des deux valeurs. Le suivant est connecté aux deux premiers 1-2-mux et le second bit du sélecteur permet de sélectionner une des deux valeurs.

Le démultiplexeur

Pour construire notre processeur, nous aurons besoin d'un circuit appelé le démultiplexeur. Un démultiplexeur prend en entrée une valeur v et un sélecteur encodée sur k bits. Un multiplexeur possède n = 2k sortie x0 à xn-1. Le principe d'un multiplexeur est de copier l'entrée v sur la sortie xi, où i est la valeur du sélecteur, et de laisser les autres sorties à 0. C'est typiquement avec un démultiplexeur qu'on peut sélectionner quelle case mémoire est modifiée par une instruction. Imaginons que nous ayons une mémoire à 8 cases. Une adresse est donc encodée sur 3 bits. Le démultiplexeur ayant pour valeur d'entrée 1 et utilisant l'adresse comme sélecteur permet d'activer un fil parmi 8 en fonction de l'adresse. Ensuite, il suffit de connecter ces fils aux différentes cases mémoire. Si un fil vaut 1, alors la case mémoire est modifiée par l'opération, alors que si le fil vaut 0, la valeur de la case mémoire n'est pas modifiée.

Dans cet exercice, nous construisons un démultiplexeur simple avec une valeur sur 1 bit et un sélecteur sur 2 bits (4 valeurs possibles). Une fois que vous aurez compris le principe de construction, vous pourrez réutiliser le démultiplexeur fourni par circuitverse que vous trouverez parmi les Decoders and Plexers.

Le 1-1-demux

Concevez un circuit nommé 1-1-demux prenant une entrée sel encodée sur 1 bit et une entrée value encodée sur un bit. Comme le sélecteur est encodé sur un bit, notre démultiplexeur a deux sorties possibles : x0 et x1. Si sel vaut 0, le multiplexeur copie value dans x0 et génère 0 sur x1 à 0. De la même façon, si sel vaut 1, le multiplexeur copie value en x1 et génère 0 sur x0.

x0 vaut 1 si value vaut 1 et sel vaut 0. x1 vaut 1 si value vaut 1 et sel vaut 1.

Le 2-1-demux

Le 2-1-demux prend entrée un sélecteur sur 2 bits et une valeur sur 1 bit. Ce sélecteur possède donc quatre sortie x0 à x3. Il se construit de façon récursive à partir du démultiplexeur avec la taille de sélecteur qui précède (1 dans notre cas). Techniquement, à l'aide d'un Splitter, il faut isoler le dernier bit du sélecteur et connecter les autres bits (1 seul dans notre cas) aux entrées sel de deux démultiplexeur 1-1-demux. Ensuite, il suffit de voir qu'il le dernier bit du sélecteur doit être utilisé pour activer l'un des deux sélecteurs.

La procédure précédente permet d'augmenter le nombre de bits du sélecteur. Pour créer un sélecteur avec une value sur K bits, il suffit de séparer les bits de value avec un Splitter, de les faire passer par K sélecteurs et de reconstruire les sorties x0 à xn-1 à partir des K sorties des sélecteurs.
En détail, l'entrée du premier sélecteur vaut vrai si value vaut 1 et si le dernier bit du sélecteur vaut 0. De façon similaire, l'entrée du second sélecteur vaut vrai si value vaut 1 et si le denier bit du sélecteur vaut 1.

Le registre

Maintenant que nous sommes capables d'effectuer des calculs, il faut pouvoir stocker dans le processeur des variables internes. Pour cela, nous construisons des registres. Un registre est un circuit possédant principalement une entrée nommée D et une sortie nommée Q. Selon son état, le registre est capable de copier l'entrée D sur la sortie Q ou de renvoyer l'ancienne sortie Q indépendamment de l'état de l'entrée. Ce phénomène de mémorisation permet au registre de conserver un état dans le temps, et donc d'agir comme une mémoire.

La bascule SR-nand

Le circuit SR-nand

Dans un nouveau circuit nommé SR-nand, reproduisez le circuit constitué de deux portes NAND présenté en figure . Ce circuit s'appelle un verrou SR (pour Set et Reset) NAND (SR NAND latch en anglais).
Le verrou SR NAND.

Recopie de la valeur d'entrée

Dans quels états sont Q et Q' quand S vaut 0 et R vaut 1 ? Et quand S vaut 1 et R vaut 0 ?

On suppose que S = 0 et R = 1. Comme S = 0, Q = 1 puisque NAND(0, *) = 1. Comme Q = 1 et R = 1, Q' = 0.

On suppose que S = 1 et R = 0. Comme R = 0, Q' = 1 puisque NAND(*, 0) = 1. Comme Q' = 1 et S = 1, Q = 0.

Il faut donc mémoriser que quand S et R sont l'inverse l'une de l'autre, Q est égal à la copie inversée de S. Nous allons utiliser plus tard ce phénomène pour copier une valeur d'entrée vers la sortie.

Mémorisation de l'ancienne valeur

On suppose que, initialement, S = 0 et R = 1. Dans quel état est le circuit si S passe à 1 ? Donnez exhaustivement l'ensemble des valeurs Q et Q' possibles en étudiant les différentes propagations possibles de l'électricité. Pour cela, étudiez le cas où la porte NAND du haut est la première à s'activer et le cas où la porte NAND du bas est la première à s'activer. De la même façon, étudiez les valeurs de sorties lorsque S=1 et R=0 initialement et que R passe à 0.

Initialement, S = 0 et R = 1, donc Q = 1 et Q' = 0 (voir question précédente). On suppose que S passe à 1.

Si le NAND du haut s'active en premier, S = 1 et Q' = 0 laissent Q à 1. Comme Q = 1 et R = 1, Q' reste à 0. Le circuit est donc stabilisé sur la sortie Q = 1 et Q' = 0.

Si le NAND du bas s'active en premier, R = 1 et Q = 1 laissent Q' à 0. Comme Q' = 0 et S = 1, Q reste à 1. Le circuit est donc stabilisé sur la sortie Q = 1 et Q' = 0.

Au résultat, quel que soit la porte activée en premier ou la vitesse de propagation de l'électricité dans les différents circuits, après le passage de S à 1, Q reste à 1 et Q' reste à 0.

On peut donc observer que sur S=0 et R=1, changer la valeur de S ne change pas la valeur de sortie. On va utiliser le faîte que la valeur de sortie ne change pas pour mémoriser une ancienne valeur.

De façon similaire, vous pouvez voir que si S=1 et R=0, changer la valeur de R ne change pas la valeur de sortie.

Le bascule JK

Le circuit JK

Comme vous avez du le constater, quand S = 1 et R = 1, on peut avoir Q = 1 et Q' = 0 si on vient de l'état S = 0 et R = 1, ou Q = 0 et Q' = 1 si on vient de l'état S = 1 et R = 0. Cette propriété est très forte car elle signifie qu'on peut "bloquer" la sortie du circuit à 1/0 ou 0/1 quand on passe S et R à 1. Pour exploiter cette propriété, on va un petit peu améliorer notre circuit. Dans un nouveau circuit nommé JK-flip-flop, reproduisez le circuit présenté en figure . Ce circuit s'appelle une bascule JK (JK flip-flop en anglais). Le nom JK semble venir du nom du concepteur de ce circuit (Jack Kirby).
La bascule JK (Circuit JK-flip-flop)

Recopie de la valeur d'entrée

Mettez l'entrée E (Enable) à 1. Dans quels états sont Q et Q' si J = 1 et K = 0 ? Dans quels états sont Q et Q' si J = 0 et K = 1 ?

Quand E = 1 et J = 1, l'entrée S de SR-nand est à 0. Quand E = 1 et K = 0, l'entrée R de SR-nand est à 1. Donc (voir questions précédentes), Q = 1 et Q' = 0. On remarque que la valeur de J est "recopiée" dans Q.

Quand E = 1 et J = 0, l'entrée S de SR-nand est à 1. Quand E = 1 et K = 0, l'entrée R de SR-nand est à 0. Donc (voir questions précédentes), Q = 0 et Q' = 1. On remarque que la valeur de J est aussi "recopiée" dans Q dans ce cas.

Mémorisation de l'ancienne valeur

Mettez E = 1, J = 0 et K = 1. Passez E à 0. Que se passe-t-il si vous changez les valeurs de J ou de K ? Expliquez. De la même façon mettez E = 1, J = 1, K = 0. Passez E à 0. Que se passe-t-il si vous changez les valeurs de J ou de K ? Expliquez.

Quand E vaut 0, les deux portes NAND envoient 1, ce qui bloque la sortie de SR-nand dans un son état courant. Si on part de J = 0 et K = 1, Q reste à 0. Si on part de J = 1 et K = 0, Q reste à 1. On voit donc que quand E=0, la sortie du JK-flip-flop reste bloquée, c'est-à-dire que l'ancienne valeur reste mémorisée.

En route vers le registre 1 bit

Le demi D-flip-flip

Comme vous l'avez sûrement déjà compris, si K est l'opposé de J, alors, quand E vaut 1, l'entrée J est recopiée dans Q. E permet aussi de bloquer le circuit : quand E passe à 0, l'état de Q ne change plus, même quand J ou K change. En d'autres termes, l'état du JK-flip-flop "mémorise" l'ancienne valeur de J. On va exploiter cette propriété pour construire une première version du registre 1 bit. Créez un nouveau circuit que vous nommerez half-D-flip-flop. Ajoutez deux entrées à ce circuit :

  • D (1 bit) est l'entrée du registre,
  • E (1 bit) permet de bloquer le circuit.

Ajoutez aussi deux sortie un bit nommées Q et Q'.

Enfin, créez un circuit qui (i) copie D dans Q quand E vaut 1 et (ii) laisse Q inchangé quand E vaut 0, même si D change.

Il suffit de connecter D à J et l'inverse de D à K puisque quand E vaut 0, la valeur de sortie de change pas.
Une demie bascule D-flip-flop

Le D-flip-flop

À haut niveau, pour construire un registre, il suffit de connecter l'entrée E de notre half-D-flip-flop à une horloge. Une horloge est un circuit qui régulièrement passe de 0 à 1 puis de 1 à 0. Lorsque l'horloge vaut 0, le half-D-flip-flop est bloqué : il renvoie la valeur mémorisée même si l'entrée D change. Lorsque l'horloge vaut 1, le half-D-flip-flop est ouvert : il copie la valeur D en sortie Q.

Tel quel, notre half-D-flip-flop n'est pas encore utilisable comme registre 1 bit. Le problème, c'est que bien souvent, on modifie l'un des registres d'entrée pendant un calcul. C'est par exemple le cas de l'instruction addu %r0, %r0, %r1 qui fait la somme de %r0 et %r1 et place le résultat dans %r0. Imaginons déjà disposer d'un half-D-flip-flop à 8 bits. Un tel half-D-flip-flop est facile à construire, il suffit d'utiliser 8 half-D-flip-flops 1 bit et de connecter de façon adéquate les différentes entrées et sorties. Ensuite, on imagine qu'on a deux registres %r0 et %r1 ayant respectivement mémorisé les valeurs 10 et 1.

Pour faire le calcul addu %r0, %r0, %r1, il faut donc connecter les sorties Q de %r0 et %r1 aux entrées d'un additionneur, et connecter la sortie de l'additionneur à l'entrée D de %r0. Lorsque E vaut 0, les registres sont bien bloqués et la valeur est mémorisée. On est en phase de calcul : le Q de %r0 émet 10 et le Q de %r1 émet 1. L'additionneur émet donc 11 sur l'entrée D de %r0. Lorsque E passe à 1, on recopie le résultat du calcul dans %r0, exactement le comportement qu'on attend. Malheureusement, à ce moment, on modifie aussi la sortie Q de %r0 qui émet maintenant 11. L'additionneur émet donc 12 qui est écrit dans %r1 puisque E vaut toujours 1. Et on recommence tant que E est égal à 1: on va donc effectuer une ribambelle d'additions alors qu'on ne voudrait en effectuer qu'une seule.

De façon à éviter ce problème, ce qu'on voudrait, c'est faire une bascule. En détail, lorsque E vaut 1, on voudrait enregistrer la valeur dans le registre mais en conservant la valeur précédente de sortie (phase d'enregistrement). Lorsque E vaut 0, on voudrait bloquer l'entrée du registre et propager en sortie du registre la valeur enregistrée pendant la phase d'enregistrement (phase de calcul).

Pour réaliser un tel circuit, il suffit de mettre deux half-D-flip-flop en contre phase comme présenté dans le figure .

Première version du circuit D-flip-flop.

Dans ce circuit, le half-D-flip-flop de gauche s'appelle le maitre et celui de droite l'esclave. En détail, sur ce circuit, lorsque clock vaut 0, on est en phase d'enregistrement. La valeur D est enregistrée dans le maître puisque son entrée E vaut 1 (inverse de clock). Pendant cette période, comme l'entrée E de l'esclave vaut 0, l'esclave émet l'ancienne valeur. En reprenant notre exemple d'addition, notre registre %r0 émet 10, l'addition donne 11 qui est envoyée dans D et enregistrée dans le maître sans modifier la sortie de l'esclave. Lorsque clock passe de 0 à 1, on entre en phase de calcul. L'esclave enregistre la valeur stockée dans le maître pendant la phase d'enregistrement précédente puisque l'entrée E de l'esclave passe à 1. Sur notre exemple d'addition, le circuit émet donc la valeur 11 ce qui lance un nouveau calcul donnant 12. Comme le maître est bloqué (son entrée E vaut 0 lorsque clock vaut 1), la valeur du registre n'est pas modifiée. La valeur 12 sera enregistrée dans le maître lorsque clock repassera à 0.

Vous pouvez noter qu'un cycle d'horloge correspond exactement à un cycle processeur, c'est ce qui est donné par la fréquence d'un processeur. Un processeur à 3GHz, par exemple, effectue 3 milliards de cycles horloges par seconde, ce qui revient à effectuer 3 milliards de calculs par secondes (3 milliards d'additions registre/registre).

Maintenant que vous devriez mieux comprendre le fonctionnement du D-flip-flop, reproduisez ce circuit dans circuitverse en lui donnant comme nom D-flip-flop.

Version complète du D-flip-flop

On va un peu faire évoluer notre D-flip-flop pour pouvoir mieux le piloter de l'extérieur. On vous demande d'avoir les entrées suivantes :

  • D (data) : la valeur d'entrée du D-flip-flop sur 1 bit,
  • reinit : un booléen permettent de réinitialiser le D-flip-flop à une valeur initiale,
  • preset : la valeur initiale du D-flip-flop,
  • E (enable) : lorsque cette valeur vaut 0, le D-flip-flop ne doit pas enregistrer de nouvelle valeur. On se servira de cette entrée dans la suite pour sélectionner le registre modifié par une instruction.
  • clock : le clock que vous aviez à la question précédente.

Techniquement, le E du maitre (half-D-flip-flop de gauche) doit valoir 1 si et seulement si reset vaut 1 ou (si clock vaut 0 et E vaut 1). Le E de l'esclave (half-D-flip-flop de droite) doit valoir 1 si et seulement si reset vaut 1 ou (si clock vaut 1 et E vaut 1). Avec un multiplexeur, faîtes aussi en sorte que, si reinit vaut 0, l'entrée D du maître soit égal à D et que sinon, cette entrée soit égale à preset.

L'idée derrière cette construction est que si reinit vaut 1, alors les deux E des deux D-flip-flops valent 1, ce qui copie directement la valeur preset dans les deux half-D-flip-flop : simultanément on enregistre la valeur dans le registre (maître) et on l'émet en sortie (esclave). Lorsque reinit vaut 0, alors la valeur D n'est copiée dans le registre que si E vaut 1 (après un passage de clock de 0 à 1).

Nous avons construit un registre 1 bit. Pour construire un registre 8 bits, il suffit d'assembler 8 registres 1 bits et de connecter correctement les entrées et sorties.
Le D-flip-flop.
Félicitations, maintenant, vous avez de quoi faire des calculs et mémoriser des valeurs, vous avez donc construit les deux circuits de base permettant de réaliser un processeur complet !

Premier calculateur et horloge

Nous avons maintenant tous les circuits permettant de construire n'importe quel processeur. Dans cet exercice, nous construisons un premier processeur extrêmement simple qui incrémente régulièrement un registre.

Dans un nouveau circuit que vous appellerez auto-adder, ajoutez un registre 8 bits. Vous pouvez trouver les registres sous le nom DFlipFlop. fournis par circuitverse parmi les Sequential Elements. Le circuit D-flip-flop que vous avez réalisé met exactement en œuvre la même interface. Vous pouvez trouver à gauche, de haut en bas les entrées D et clock. En dessous, de gauche à droite, vous avez les entrées Enable (E chez nous), Preset et Asynchronous Reset (reset chez nous). À droite, de haut en bas, vous avez les sorties Q et Q'.

Pour configurer votre registre pour avoir 8 bits, cliquez sur le registre et modifiez son BitWidth. Ensuite, connectez :

  • une entrée constante (entrée du type ConstantVal, 5ième icône parmi les entrées) sur 8 bits (propriété BitWidth) valant 1 (pour changer la valeur de la constante, double-cliquez dessus) à D,
  • une horloge que vous trouverez parmi les Sequential Elements (8ième icône) à clock ,
  • une entrée constante sur 1 bit valant 1 à Enable,
  • une entrée constante sur 8 bits valant 0 à Preset,
  • une entrée de type bouton (deuxième icône parmi les entrées) à Asynchronous Reset. Quand vous appuyez sur le bouton, son état passe à 1, et lorsque vous le relâchez, son état revient à 0.

Vérifiez que (i) le registre passe à 1 lorsque l'horloge effectue un cycle et (ii) que le registre repasse à 0 lorsque vous appuyez sur le bouton.

Modifiez votre circuit de façon à incrémenter le registre à chaque cycle.

Pensez d'abord à utiliser le Adder fourni par circuitverse. Ensuite, il suffit de connecter Q à l'entrée du Adder, la constante 0 sur 8 bits à l'autre entrée, la constante 1 sur 1 bit à Cin et de connecter la sortie du Adder à l'entrée D du registre.
Un calculateur incrémentant une valeur à chaque cycle.
Féclitations ! Votre ordinateur ne fait peut-être pas encore un calcul très folichon, mais c'est un vrai calculateur numérique !

Conception d'une mémoire (optionnel)

Cet exercice a pour but de vous montrer comment on peut concevoir une mémoire. Bien que les mémoires DDR actuelles utilisent une technologie différente, nous allons concevoir notre mémoire comme un ensemble de registres. Le but étant de comprendre comment on conçoit une mémoire, nous allons nous restreindre à une mémoire à 4 cases de 4 bits chacune. Le but est de comprendre le principe de construction, pas de passer des heures à tirer des fils.

Créez une mémoire de 4 cases de 4 bits chacune. Votre mémoire doit prendre les entrées suivantes :

  • A : une adresse encodée sur 2 bits pour adresser 4 cases,
  • DI (Data In) : une valeur encodée sur 4 bits,
  • W : un booléen indiquant si il faut écrire la valeur DI à l'adresse A,

En sortie, votre circuit doit fournir une valeur DO encodée sur 4 bits correspondant au contenu de la case A.

Votre mémoire doit être synchrone, c'est-à-dire qu'elle doit répondre immédiatement, indépendamment d'une horloge. Pour cela, il suffit d'utiliser les entrées Preset et Asynchronous Reset pour écrire immédiatement dans les registres. Techniquement, sur chacun des quatre registres, il suffit de connecter une entrée constante valant 0 à chaque entrée E, ce qui fait que le registre va ignorer les entrées D et clock que vous n'avez donc pas besoin de connecter. Ensuite, il faut connecter l'entrée DI du circuit aux Preset de chacun des registres, et alimenter le Asynchronous Reset du Aième registre.

Pensez à utiliser un démultiplexeur pour savoir quel Asynchronous Reset il faut alimenter, et un multiplexeur pour sélectionner la valeur DO.

La valeur à envoyer sur les Asynchronous Reset est la valeur W.

Assemblage final

Nous arrivons enfin à la dernière étape de notre conception d'un petit processeur. Notre petit processeur va utiliser 8 registres numérotés de 0 à 7. Le registre 7 nous servira à stocker le pointeur de code courant et nous l'appelerons %PC. Les registres 5 et 6 nous servirons à stocker l'instruction en cours d'exécution car notre processeur prend en entrée des instructions encodées sur 16 bits. Nous nommerons donc le registre 5 %insnl (partie basse) et le registre %insnh (partie haute). Les 5 autres registres %r0 à %r5 sont utilisables pour effectuer d'autres calculs.

De façon à simplifier le design, en interne, notre processeur va exécuter une instructions en 5 cycles :

  • Cycle 0 : chargement du contenu de %PC dans %insnl,
  • Cycle 1 : ajout de 1 à %PC,
  • Cycle 2 : chargement du contenu de %PC dans %insnh,
  • Cycle 3 : ajout de 1 à %PC,
  • Cycle 4 : exécution de l'instruction (%insnl, %insnh).

Pour savoir à quelle étape de l'exécution nous sommes, nous utiliserons un registre annexe nommé %mini-pc. Le processeur va donc, à chaque cycle, effectuer une boucle qui incrémente %mini-pc et qui réinitialise %mini-pc à 0 lorsqu'il atteint 5.

On considère deux types d'instructions : les instructions qui accèdent à la mémoire et les instructions qui effectuent des calculs avec l'ALU. Les instructions qui accèdent à la mémoire sont codées de la façon suivante :

  • Bit 0 : 0 pour une lecture, 1 pour écriture,
  • Bit 1 : vaut 1 pour un accès mémoire,
  • Bits 2 à 4 : ignorés,
  • Bits 5 à 7 : registre de sorties pour une lecture mémoire,
  • Bits 8 à 10 : registre contenant l'adresse accédée,
  • Bits 11 à 13 : registre contenant la valeur à écrire pour écriture mémoire,
  • Bits 14 et 15 : ignoré.

Par exemple, l'instruction [00 000 010] [100 000 1 0] lit le contenu de la mémoire se trouvant à l'adresse %r2 et écrit le résultat dans le registre %r4, ce qui correspond à l'instruction lw %r4, (%r2). De façon similaire, l'instruction [00 101 010] [000 000 1 1] écrit le registre %r5 à l'adresse %r2, ce qui correspond à l'instruction sw %r5, (%r2).

Pour les instructions qui accèdent à l'ALU :

  • Bit 0 : 0 pour un calcul registre/registre/registre, par exemple, addu %r1, %r2, %r3 qui additionne %r2 et %r3 et stocke le résultat de %r1, 1 pour un calcul avec une valeur immédiate, par exemple, addiu %r0, 1 qui ajoute 1 au registre %r0,
  • Bit 1 : vaut 0 pour une instruction qui utilise l'ALU,
  • Bit 2 à 4 : 3 premiers bits de la fonction pour l'ALU,
  • Bit 4 à 7 : registre de sortie. Pour le cas d'une instruction immédiate (bit 0 à 1), c'est aussi le registre d'entrée,
  • Bit 8 à 15 : soit la valeur immédiate pour une instruction immédiate, soit la décomposition suivante :
    • Bits 8 à 10 : registre d'entrée 1,
    • Bits 11 à 13 : registre d'entrée 2,
    • Bits 14 à 15 : 2 derniers bits de la fonction pour l'ALU (dans le cas d'une instruction immédiate, on considère que ces deux bits valent 0 puisque cette zone est occupée par la valeur immédiate).

Par exemple, l'instruction [10 101 110] [001 010 0 0] correspond au calcul [10010] (bits 15, 14, 4, 3, 2) avec pour entrée les registres %r6 et %r5 et pour sortie %r1. L'instruction [10101110] [001 010 0 1] correspond au calcul [00010] (0, 0 et bits 4, 3, 2) avec pour entrée le registre %r1 et la valeur immédiate [10101110] et pour sortie %r1.

Le circuit REG

Maintenant qu'on a tous les composants pour construire notre processeur, il n'y a plus de difficulté technique pour le réaliser, sauf une majeure : il faut être capable de gérer des dizaines voir des centaines de fils pour relier nos différents composants. Au lieu de gérer ces fils de façon aléatoire, nous allons concevoir des circuits intermédiaires qui ne servent que à masquer ces fils. Nous commençons ici par encapsuler les registres derrière un circuit que nous appelons REG. Ce circuit sera beaucoup plus commode à utiliser qu'un registre fourni par circuitverse.

On vous demande donc de créer un circuit REG ayant un fonctionnement extrêmement simple. Il doit&nbsp:

  • Générer une valeur constante de 8 bits égale à 0 en sortie nommée P (pour Preset),
  • Prendre une entrée nommée C (Clock) sur 1 bit et la répliquer deux fois avec le même nom C et C,
  • Prendre une entrée nommée R (Reset) sur 1 bit et la répliquer deux fois avec le même nom R et R,
  • Prendre une entrée nommée Q qui sera connecté à un registre et la répliquer une fois sous le nom out (sortie du registre),
  • Prendre une entrée nommée in (entrée du registre) et la répliquer une fois sous le nom D qui sera connecté à un registre,
  • Prendre une entrée W (Write) et la répliquer une fois sous le nom E.

Le circuit en lui-même n'est donc pas très utile. Ce qui sera important sera de travailler sur l'apparence de notre registre (Edit Layout) à la question suivante.

Utilisation du circuit REG

Nous allons maintenant commencer à créer notre processeur. Créez donc un circuit nommé CPU. Dans ce circuit ajouté le registre 8 bits mini-pc. Ensuite, éditer l'apparence du circuit REG de façon à pouvoir le connecter comme représenté sur la figure .

Utilisation du circuit REG.

En détail, il faut laisser les entrées C et R à gauche. Ensuite, il faut mettre deux sorties C et R en haut, et deux sorties C et R à droite. En haut, il faut aussi placer les sorties D, Eet P, ainsi que l'entrée Q. En bas, il faut placer la sortie out et l'entrée in.

Vous pouvez aussi remarquer que dans ce circuit, on a une horloge configurable : quand Pause est à 0, l'horloge automatique est désactivée et l'entrée Clock permet d'avancer manuellement l'horloge. Ce mécanisme vous servira pour exécuter pas à pas votre processeur.

Vérifier que votre CPU fonctionne correctement : quand vous appuyer sur le bouton, le registre mini-pc doit être réinitialisé à 0, et quand vous changez la valeur d'entrée connectée à in, la valeur du registre doit changer après un cycle horloge.

La boucle du processeur

Nous mettons maintenant en place la boucle principale du processeur, c'est-à-dire le circuit permettant d'incrémenter à chaque cycle horloge le registre mini-pc et de le remettre à 0 quand il atteint 5.

Créez un circuit nommé MINI-PC prenant en entrée une registre 8 bits in (valeur actuelle du PC). Votre circuit doit générer trois sorties différentes :

  • out est la valeur incrémentée du mini-pc. Lorsque cette valeur atteint 5, vous devez générer 0.
  • mini-pc est une copie de out. C'est cette valeur qui va être utilisée dans le reste du processeur.
  • W est un sortie valant toujours 1 car à chaque cycle horloge, le mini-pc est incrémenté.

Le plus simple pour le mini-pc est d'utiliser un démultiplexeur pour savoir quand le registre vaut 5, et de connecter la 5ième sortie du démultiplexeur à un multiplexeur permettant de choisir entre in + 1 et 0.

Ensuite, configurez l'apparence de votre circuit de façon à pouvoir le connecter au reste du processeur comme représenté sur la figure . Si votre circuit est correct, le mini-pc devrait être incrémenté à chaque cycle horloge, et devrait revenir à 0 lorsqu'il atteint 5.

Utilisation du circuit MINI-PC.

Le circuit REGISTERS

Nous arrivons maintenant à un partie qui demande d'être un particulièrement attentif : la conception d'un circuit pour gérer nos 8 registres. Le but, à la fin de cette question, est de pouvoir utiliser le circuit REGISTERS dans votre CPU comme présenté en figure .

Utilisation du circuit REGISTERS.

Pour cela, il faut simultanément concevoir le circuit REGISTERS, configurer son apparence et vérifier que le circuit se connecte bien aux 8 registres. Nous vous donnons les étapes à suivre dans la suite.

Pour commencer, ajouter 8 registres 8 bits et leurs 8 circuits REG comme représenté sur la figure. Pensez à connecter l'horloge et le reset de celui de gauche. Vous remarquerez qu'en accolant nos circuits REG, on transmet l'horloge et le reset de REG en REG.

Ensuite, créez le circuit REGISTERS avec 8 entrées nommées de Q0 à Q7 et connectées directement aux sorties nommées r0, r1, r2, r3, r4, insnl, insnh et pc. Ajoutez ce circuit dans CPU et faîtes en sorte de pouvoir le connecter :

  • Les Q0 à Q7 doivent être pouvoir être directement connectés aux Q des REGS.
  • Les sorties doivent être en bas, plutôt à gauche. Vous ajusterez leur position quand vous ajouterez de nouveaux circuits.

Une fois que l'apparence est satisfaisante, il faut passer à l'étape suivante. Il faut ajouter une entrée in à REGISTERS et connectées à 8 sorties nommées I0 à I7. Cette valeur est celle calculée par une instruction, et donc celle qui sera mémorisée dans le registre de sortie d'une instruction. Les sorties I0 à I7 doivent être connectées facilement aux entrées I des REG. L'entrée in doit être placée à droite du circuit comme indiqué sur la figure.

Enfin, nous allons nous occuper de sélectionner le registre écrit par une instruction. Ajoutez deux entrées, l'une nommée W sur un bit, l'autre sel sur 3 bits. W sera a vrai lorsque l'instruction écrit dans un registre (techniquement, toute instruction, sauf un sw, c'est-à-dire un store word). L'entrée sel indique quel registre doit écrit avec la valeur in. Ensuite, créez 8 sorties W0 à W7. Votre circuit doit copier la valeur W dans le Wii est la valeur de sel. Il faut placer les Wi en haut de façon à pouvoir les connecter aux entrées W des REG. W et sel doivent se trouver en bas, à droite des valeurs des registres, comme indiqué sur la figure.

Utilisez un démultiplexeur pour sélectionner la sortie Wi adéquate.

Vous pouvez vérifier que votre circuit est correcte en activant l'horloge. À ce moment, vous devriez voir le mini-pc avancer. Ensuite, vous pouvez modifier la valeur de n'importe quel registre avec W, sel et in. Par exemple, si vous mettez la valeur 1 dans in, la valeur 011 dans sel et la valeur 1 dans W, vous devriez écrire 1 dans le registre %r3.

Le circuit MICROCODE

Maintenant que nous pouvons manipuler correctement nos registres, nous allons pouvoir concevoir le microcode exécuté par notre processeur. Techniquement, votre processeur doit exécuter :

  • lw %insnl, (%pc) quand mini-pc vaut 0 (encodée a0 07),
  • addiu %pc, 1 quand mini-pc vaut 1 (encodé eb 01),
  • ow %insnh, (%pc) quand mini-pc vaut 2 (encodé c0 07),
  • addiu %pc, 1 quand mini-pc vaut 3 (encodé eb 01),
  • %insnl/%insnh quand mini-pc vaut 4 (donné par les registres).

Votre circuit doit donc prendre en entrée insnl et insnh que vous connecterez à votre circuit REGISTERS et mini-pc que vous connecterez à votre circuit MINI-PC. En sortie (à mettre en bas du circuit), votre circuit doit générer l'instruction adéquate.

Pour générer les instructions, vous pouvez soit utiliser des valeurs constantes et des mutliplexeurs, mais il est plus rapide et plus simple d'utiliser une ROM fournie par circuitverse. Une ROM est un circuit générant des constantes. Il est plus agréable à utiliser car on peut stocker des valeurs directement en hexadécimal. Une ROM se trouve dans la catégorie Misc des Circuits Elements. Vous pouvez modifier la valeur d'une cellule de la ROM en cliquant dessus et en saisissant une valeur hexadécimale directement. Pour notre microcode, nous avons besoin de deux ROMs, l'une pour la partie basse des instructions, l'autre pour la partie haute. Derrière, il faut savoir que l'entrée A d'une ROM donne l'adresse accédée et que la sortie D donne la valeur stockée. Il faut aussi savoir qu'il faut mettre une constante 1 en entrée E pour activer la ROM.

Ajustez l'apparence de votre circuit MICROCODE et éventuellement celle de votre circuit REGISTERS de façon à pouvoir le connecter comme indiqué en figure .

Connexion du circuit MICROCODE.

Le circuit DECODER

À cette étape, nous concevons le circuit DECODER qui est capable de décoder une instruction. Ce circuit prend en entrée les 8 registres : les insnl et insnh générés par le circuit MICROCODE et les r0, r1, r2, r3, r4 et pc générés par le circuit REGISTERS. En sortie, le circuit génère plusieurs sorties permettant de piloter le reste du processeur :

  • WR  vaut vrai si l'instruction écrit dans un registre. C'est le cas de toutes les instructions, sauf les store word. Pour identifier un sw, il suffit de se rappeler que le bit 0 de l'instruction vaut 1 (écriture en mémoire) et que le bit 1 de l'instruction vaut 0 (instruction d'accès à la mémoire),
  • WM : vaut vrai si l'instruction écrit dans la mémoire. C'est aussi uniquement le cas des instructions store word,
  • A : donne l'adresse pour une instruction d'accès à la mémoire. Il suffit de retourner la valeur du registre dont le numéro se trouve entre les bits 8 et 10 de l'instruction.
  • DI  la valeur écrite en mémoire pour une instruction d'accès à la mémoire. Il suffit de retourner la valeur du registre dont le numéro se trouve entre les bits 11 et 13 de l'instruction.
  • mem? : vaut vrai si l'instruction accède à la mémoire. C'est le cas si le bit 1 de l'instruction vaut 0.
  • fun : donne la fonction à exécuter par l'ALU dans le cas d'une instruction de calcul. Il suffit de regrouper les bits 2 à 4 de l'instruction avec :
    • Les bits 14 et 15 de l'instruction si l'instruction n'est pas de type calcul immédiat,
    • La valeur 00 sinon. Pour savoir si une instruction est de type immédiat, il suffit de se rappeler que dans ce cas, le bit 0 est 1 (immédiat) et le bit 1 à 1 (ALU).
  • v0 : le premier paramètre de l'opération dans le cas d'une instruction de calcul. Si l'instruction est de type calcul immédiat, on trouve le numéro de registre entre les bits 5 à 7, sinon, on le trouve entre les bits 8 à 10,
  • v1 : le second paramètre de l'opération dans le cas d'une instruction de calcul. Si l'instruction est de type calcul immédiat, il suffit de renvoyer les bits 8 à 15. Sinon, il faut renvoyer la valeur du registre dont le numéro se trouve entre les bits 11 à 13.
  • Sel : donne le numéro de registre de sortie, que ce soit pour un accès mémoire ou un calcul (immédiat ou non). On trouve se numéro entre les bits 5 à 7.
Ce circuit n'est pas difficile à faire, mais la moindre erreur dans ce circuit rendra aléatoire l'exécution de vos instructions. Comme ce sont des erreurs difficiles à comprendre et corriger, faîtes plusieurs tests pour vous convaincre que votre DECODER se comporte de façon satisfaisante.

Vous pouvez brancher votre décodeur en dessous de MICROCODE. comme indiqué sur la figure

Utilisation du circuit REG.

Connexion de l'ALU

Vous pouvez maintenant brancher votre ALU. L'entrée de l'ALU est donnée par le DECODER (fun, v0 et v1). En sortie de l'ALU (res, placez un multiplexeur permettant de choisir soit res, soit la valeur 0 sur 8 bits. Lorsque mem? vaut 0, il faut choisir res et lorsque mem? vaut 1, il faut choisir 0. Vous pouvez après directement brancher la sortie du multiplexeur à l'entrée in de REGISTERS. Si votre circuit est correct, vous devriez voir le registre PC avancer (lorsque mini-pc passe à 2 et lorsque mini-pc passe à 4). Il ne se passe rien d'autre car, comme vous ne lisez pas en mémoire (vous renvoyez 0 dans ce cas), il n'y a rien d'autre à exécuter (remarquez que l'instruction 0/0 est une instruction d'accès mémoire qui elle-même renvoie 0).

Connexion de la mémoire

Il ne vous reste plus qu'à connecter la mémoire pour finaliser votre processeur. Pour cela, vous allez devoir gérer une ROM et une RAM. La ROM fait office de BIOS dans notre processeur. Elle contient du code permettant de démarrer notre processeur et qui accède à la RAM.

Comme déjà indiqué, vous trouverez la ROM parmi les circuit Sequential Elements de circuitverse. La RAM se trouve aussi dans la même zone, mais il faut penser à scroller vers le bas pour la voir (ou utiliser le bouton de recherche en mettant ROM). Configurez votre RAM à 256 octets car vos adresses sont sur 8 bits.

Lorsque l'adresse est comprise entre 0 à 31, vous devez accéder à la ROM. Sinon, vous devez accéder à la RAM. Pour sélectionner la bonne sortie (celle de la ROM ou de la RAM), il suffit d'utiliser un multiplexeur. Pensez à réutiliser le circuit z4? que vous aviez conçu en début de TP : ce circuit est bien commode pour savoir si les bits 4 à 7 de l'adresse sont à 0.

Pour finir, il faut mettre un peu de code dans votre ROM. Nous vous suggérons le code suivant qui exécute l'équivalent de while(true) x++ où la variable x se trouve à l'adresse 0xff :

  • à l'offset 0 : 0b ff qui correspond à addiu %r0, 0xff, ce qui revient à charger 0xff dans %r0,
  • à l'offset 2 : 20 00 qui correspond à lw %r1, (%r0), ce qui revient à charger la mémoire se trouvant en %r0 (0xff) dans le registre %r1,
  • à l'offset 4 : 2b 01 qui correspond à addiu %r1, 1, ce qui revient à incrémenter %r1,
  • à l'offset 6 : 01 08 qui correspond à sw %r1, (%r0), ce qui revient à écrire %r1 à l'adresse %r0 (0xff),
  • à l'offset 8 : eb f8 qui correspond à addiu %pc, -8, et fait donc sauter le code à l'offset 2, et donc boucler notre programme.
Si vous êtes arrivé au bout de cet exercice, un grand bravo !
Depuis la fin des années 70, on n'écrit plus de processeur directement avec des portes logiques, sauf pour comprendre sa mise en œuvre comme dans cet exercice. En effet, dans notre exercice, nous avons fait l'hypothèse que MEM avait la même fréquence que le reste du processeur, et que ALU était capable d'effectuer un calcul en moins d'un demi-cycle. Dans des machines réelles, ce n'est pas le cas. Il faut donc synchroniser des circuits qui effectuent des opérations à différentes vitesses avec la même horloge, voir des circuits qui ont des horloges différentes quand on communique entre la mémoire et le processeur (on parle de circuit multi-clock domains). Comme gérer ces synchronisations à la main devient rapidement très difficile, on utilise aujourd'hui des langages plus expressifs comme VHDL ou Verilog.

Une ALU complète

Si vous manquez d'idées pour occuper vos longues soirées d'hiver, nous vous proposons enfin de terminer votre ALU pour qu'elle soit capable d'exécuter un peu plus d'opération.

On va fonctionner en deux phases. On va commencer par créer une ALU capable d'effectuer des additions et des opérations binaires. Ensuite, on va ajouter la possibilité de déplacer les bits d'une valeur binaire à gauche ou à droite.

Addition et opérations binaires

Pour cette ALU, nous allons nous servir des bits 0, 1 et 2. Ils vont avoir la signification suivante&nbp;:

  • Bit 0 : inverse les bits de v0, de v1 et le résultat du calcul,
  • Bit 1 : 0 pour un AND, 1 pour un ADD,
  • Bit 2 : inverse les bits de v1 et génère une retenue en entrée du circuit ADD.

Si le bit 0 et le bit 2 sont activés, on n'inverse qu'une unique fois v1.

Pour le bit 2, vous pouvez remarquer que si vous faîtes X + ~X + 1, vous obtenez 0. C'est à dire que -X = ~X + 1. Le bit 2 permet donc de considérer -v1 au lieu de v1 comme entrée dans une addition. Ensuite, si vous regardez attentivement les combinaisons possibles, vous devriez voir que :

  • si func = 000 , alors res = v0 AND v1,
  • si func = 001 , alors res = v1 OR v1,
  • si func = 010 , alors res = A + B,
  • si func = 011 , alors res = ~(~A + ~B) = A + B + 1,
  • si func = 100 , alors res = A & -B,
  • si func = 101 , alors res = A | -B,
  • si func = 110 , alors res = A - B,
  • si func = 111 , alors res = A + B - 1.

Décalage d'un bit à droite

Nous pouvons aussi ajouter la possibilité de déplacer les bits d'un nombre à gauche ou à droite. On va commencer par créer un circuit nommé shr-1 capable de déplacer les bits d'un nombre d'un cran à droite. On considère 3 cas différents spécifiés par les entrées r? et a? codées sur 1 bit chacune :

  • Si r? vaut 0 et a? vaut 0 : on fait un décalage logique, en sortie, le bit le plus à gauche vaut 0,
  • Si r? vaut 0 et a? vaut 1 : on fait un décalage arithmétique, c'est à dire qu'on souhaite conserver le bit de signe (bit le plus à gauche). Techniquement, il suffit de faire un décalage à droite sans modifier le bit de signe,
  • Si r? vaut 1 et a? vaut 0 : on fait une rotation. Le bit 0 passe en position 7.
  • Si r? vaut 1 et a? vaut 1 : c'est une entrée illicite et vous pouvez renvoyer ce que vous voulez.

Il faut aussi considérer une entrée do? avec le comportement suivant :

  • Si do? vaut 0, alors le circuit renvoie le nombre en entrée sans le modifier,
  • Si do? vaut 1, alors le circuit renvoie le nombre en entrée décalé d'un bit à droite en suivant la spécification donnée par le couple a?/r?.
Lorsque vous allez concevoir shr-2 et shr-4, vous vous apercevrez qu'il est commode d'avoir une sortie do? répliquant l'entrée do? en sortie.

Décalage à droite

Pour décaler un entier sur 8 bits de n bits, on considère plusieurs cas :

  • Si n est plus petit strictement que 8, on peut décomposer le décalage de n bits et plusieurs déclaages de 2k bits. Par exemple, un décalage de 5 bits revient à faire un décalage de 1 bits puis un décalage de 4 bits.
  • Sinon, si r? vaut 1 et a? vaut 0, décaler de n bits revient à décaler de n modulo 8 bits : il suffit de ne considérer que les trois premiers bits de n.
  • Sinon, si r? vaut 0 et a? vaut 0, il suffit de renvoyer 0,
  • Sinon, c'est que r? vaut 0 et a? vaut 1, il suffit de renvoyer 0 si le bit de gauche à 0, et 11111111 sinon.

Commencez par construire shr-2 permettant de décaler un entier de 2 bits à partir de deux shr-1, puis shr-4 permettant de décaler un entier de 4 bits à partir de deux shr-2. Enfin, construisez le circuit shr permettant de décaler une entier de n bits où n est quelconque.

Image not found
Le circuit shr-2.
Image not found
Le circuit shr-4.
Image not found
Le circuit shr.

Le Barrel-shifter

Une fois qu'on remarque qu'un décalage à gauche revient à inverser les bits, décaler à droite et ré-inverser les bits, on peut concevoir un circuit Barrel-shifter permettant d'effectuer des décalages à gauche et à droite. Après avoir conçu un circuit revert permettant d'inverser les bits d'un entier 8 bits, mettez en œuvre un Barrel-shifter prenant, outre une valeur et un décalage, 3 entrées : l? (left?) pour un décalage à gauche, r? pour une rotation et a? pour un décalage arithmétique au lieu d'un décalage logique.

Image not found
Le circuit revert.
Image not found
Le circuit barrel-shifter.

Une ALU complexe

Vous pouvez maintenant concevoir une ALU plus complexe en considérant les cas suivants :

  • Bit 4 à 0 : on utilise l'ALU ADD-AND,
  • Bit 4 à 1 : on utilise un barrel-shifter. Dans ce cas, on peut utiliser les bits 0, 1 et 2 comme entrées l?, r? et a?.

Pour aller plus loin

Pour avoir un processeur complet, il faut aussi être capable d'exécuter des sauts conditionnelles. Pour cela, le plus simple est de commencer par ajouter un nouveau registre appelé flag permettant de stocker le bits d'état du dernier calcul. Il est conseillé de placer ce registre à gauche du mini-pc car la connectique dans la partie gauche du processeur est déjà prévue pour accueillir ce registre.

Ensuite, on va servir de l'état de ce drapeau pour savoir si une condition est remplie. Par exemple, quand A + B - 1 (function 111) a un bit de retenu, c'est que A . On peut donc s'en servir pour comparer deux nombres. On peut aussi ajouter, à l'ALU (circuit ADD-AND) la possibilité d'activer un bit z? dans le flag pour savoir si le résultat est 0. En calculant A - B (function 110), on peut savoir si deux nombres sont égaux.

Partant de là, on peut imaginer pouvoir ajouter un prefix à une instruction. Techniquement, dans MICROCODE, après avoir chargé la première instruction, si %insnl utilise la fonction 100 qui ne sert absolument à rien (A & ~B semble bien inutile comme calcul), on rentre dans un mode spécial. On considère qu'il faut réinitialiser mini-pc à 0, et on utilise les bits 5 à 7 de l'instruction comme masque. Si l'un des bits du masque est activé dans flag, c'est que la condition est remplie, sinon, c'est que la condition est fausse. Dans le cas où la condition est fausse, on peut activer le bit 7 du flag qui indique qu'il ne faut activer ni WR?, ni WM? dans DECODER lorsqu'on exécutera l'instruction insnl/insnh, ce qui revient à ignorer l'instruction.

Par exemple, pour coder jeq %r0, %r1, lab, il faut en fait coder subu %r4, %r0, %r1 (on considère que le registre %r4 est un registre poubelle ne servant à rien), puis [010 100 00] addiu %pc, lab. Le prefix 100 fait entrée le MICROCODE en mode spécial. On regarde le flag et on voit que le bit 1 du flag n'est pas actif (010) si r0 est différent de r1. La condition est donc fausse, on active le bit 7 du flag, et, lorsqu'on exécute le addiu %pc, lab, on ignore l'écriture dans le registre %pc, ce qui revient à ignorer l'instruction.

Pour cet question, vous n'avez malheureusement pas de corrigé type, désolé !