Unité Centrale 4 bits architecture d’un ordinateur c Jacques Duma 19 octobre 2012 Ce document décrit l’architecture d’un tout petit ordinateur très simple basé sur un processeur 4 bits. L’application Web "UC4bits" simule cet ordinateur et permet de le faire fonctionner de façon interactive : Pour montrer les fonctions disponibles sur cet ordinateur et expliquer comment les utiliser, on présente d’abord section 1 un exemple très simple que l’on réalise pas à pas. On décrit ensuite avec précision section 2 l’architecture de la machine, ses registres et sa mémoire. Puis on définit section 3 l’ensemble des seize instructions du langage de programmation machine. Un mode d’emploi de l’application est présenté section 4. Enfin, pour terminer, on propose section 5 d’autres exemples classés par ordre de difficulté croissante. 1 Table des matières 1 Exemple simple 1.1 Le problème . . . . . . . . . . . . . . . . . . 1.2 Algorithme de résolution du problème . . . . 1.3 Adaptation de l’algorithme au processeur . . 1.4 Écriture de l’algorithme en langage machine 1.5 Codage de l’algorithme en binaire . . . . . . 1.6 Programmation de la machine et tests . . . 1.7 Test de l’algorithme . . . . . . . . . . . . . . 1.8 Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Architecture de la machine 2.1 Registres du processeur . . . . . . . . . . . . . . . . 2.1.1 Registres de contrôle . . . . . . . . . . . . . 2.1.2 Registre de calcul et bascule de débordement 2.1.3 Registre d’Entrée/Sortie . . . . . . . . . . . 2.2 Mémoire . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Cycle d’exécution des instructions . . . . . . . . . . 3 Langage machine 3.0 0000 : STOP . . . . . . . . . . . . 3.1 0001 : JIFOVR Jump if overflow 3.2 0010 : JUMP . . . . . . . . . . . . 3.3 0011 : JIFZ Jump if zero . . . . 3.4 0100 : JIFNZ Jump if not zero . 3.5 0101 : READ . . . . . . . . . . . . 3.6 0110 : WRITE . . . . . . . . . . . . 3.7 0111 : LDA Load A . . . . . . . 3.8 1000 : STA Store A . . . . . . . 3.9 1001 : ADDA Add A . . . . . . . 3.10 1010 : SUBA Subtract A . . . . . 3.11 1011 : INC Increment . . . . . . 3.12 1100 : DEC Decrement . . . . . 3.13 1101 : LSHIFT Left shift . . . . 3.14 1110 : RSHIFT Right shift . . . 3.15 1111 : CMPL Complement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 5 6 7 7 7 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 9 9 10 10 11 11 . . . . . . . . . . . . . . . . 11 12 13 13 13 13 14 14 15 15 15 15 16 16 16 17 17 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Mode d’emploi de l’application 18 4.1 Programmation . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2 Exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2 4.3 Assembleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 5 Autres exemples 5.1 Complémenter bit à bit . . . . . 5.2 Clignoter les diodes N fois . . . 5.3 Additioner de deux entiers . . . 5.4 Tripler un nombre sans retenue 5.5 Tripler un nombre avec retenue 5.6 Multiplier deux nombres . . . . 5.7 Somme des N premiers entiers . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 21 22 23 25 27 29 32 1 Exemple simple Cette Unité Centrale 4 bits contrôle une mémoire de 16 octets dans laquelle on peut coder de petits programmes écrits à l’aide d’un jeu de 16 instructions machine. Un premier exemple simple va être décrit en détail pour montrer ce qu’il est possible de faire avec ce minuscule ordinateur. 1.1 Le problème On se propose de lire deux nombres et d’afficher le plus grand. En réalité, on se propose d’écrire un petit programme que l’on enregistrera dans la mémoire de la machine. On lancera alors l’exécution de ce petit programme. Et, en principe, il devra résoudre le problème. Donc, précisons d’abord le sens que l’on donne à tous les mots du problème qui a été proposé ci-dessus : – on appelle nombre un entier compris entre 0 et 255, c’est à dire un entier qu’il est possible de coder en binaire sur un octet. – lire signifie que lors de l’exécution du programme une interruption se produira pour permettre à l’utilisateur de donner un nombre à la machine qui pourra alors le lire, c’est à dire l’enregistrer dans sa mémoire. – afficher signifie que lors de l’exécution du programme une interruption se produira pour permettre à la machine d’afficher un octet de sa mémoire. Les opérations d’enregistrement dans la mémoire, de lecture ou d’affichage aussi appelée Entrées/Sorties, se font sur le Panneau de commande présenté page 18, à l’aide de 8 diodes bleues (les 8 bits d’un octet) qu’il est possible d’allumer (mettre à 1) ou d’éteindre (mettre à 0) avant de valider la configuration. La conception du programme va s’effectuer pas à pas, en langage courant d’abord, de façon symbolique ensuite, puis enfin en codant le problème en binaire comme l’exige la machine. On découvrira ainsi petit à petit le principe des contraintes imposées par l’utilisation d’un ordinateur. 1.2 Algorithme de résolution du problème Un minimum d’expérience en algorithmique permet assez facilement de décrire la solution du problème : 4 Lire M Lire N Si M > N alors Afficher M Sinon Afficher N Fin Si l’on consulte la liste des instructions disponibles section 3 page 11 on constate l’absence d’un test de supériorité. Pour tester que M > N il sera nécessaire de calculer la différence M − N et de tester que cette opération s’est déroulée sans débordement. En informatique on parle de débordement (ou overflow ) lorsqu’une opération arithmétique dépasse la capacité du processeur. Dans notre cas, comme on traite uniquement des entiers compris entre 0 et 255, un débordement se produira dans le calcul de M − N si N > M. 1.3 Adaptation de l’algorithme au processeur On va réécrire l’algorithme, toujours dans un langage symbolique, mais plus proche de celui de la machine. Dans ce qui suit, les variables représentées par les lettres M et N représente des octets de la mémoire, mais certaines contraintes liées à l’architecture de la machine apparaissent. Comme il n’existe pas de test de supériorité, il faudra le remplacer par une soustraction et un test de débordement. Selon le résultat du test, la suite du programme sera située à deux endroits distincts de la mémoire, donc il sera nécessaire d’effectuer un saut à une adresse particulière de la mémoire que l’on va étiqueter. Comme il n’est pas possible de soustraire directement des octets de la mémoire, il faudra utiliser le registre de calcul de l’unité centrale. Ce registre nommé A est décrit sous-section 2.1. L’algorithme de résolution du problème va prendre la fore suivante : Lire M Lire N A ← M A ← A - N Saut_Si_Débordement Aller_à SUITE Afficher N Stop SUITE Afficher M Stop 5 Ci-dessus M et N représentent des variables informatiques habituelles. Pour notre machine ces variable seron situées en mémoire aus adresses m et n. A représente l’accumulateur, registre de calcul de l’unité centrale. suite est une étiquette qui pointe sur une adresse mémoire, celle de l’octets qui contiendra l’instruction Afficher m. Lors de l’exécution normale du programme, les instructions sont exécutées successivement, sauf lorsqu’on rencontre des instruction de saut. L’instruction Saut_Si_Débordement est un saut conditionnel. Si il n’y a pas de débordement lors du calcul précédent, on exécute ensuite l’instruction suivante. Si, par contre il y a débordement, on saute l’instruction suivante pour exécuter celle d’après. L’instruction Aller_à suite est un saut immédiat. L’instruction exécutée ensuite sera celle qui est située à l’adresse indiquée par l’étiquette suite. Pour continuer la mise au point du programme, il est nécessaire de comprendre comment il sera exécuté par l’unité centrale. 1.4 Écriture de l’algorithme en langage machine Les instructions du programme seront placées dans les octets pris successivement en mémoire à partir de l’adresse 0000, c’est-à-dire l’adresse contenue initialement dans le registre INDX après "Reset". Pour éviter les conflits, il est usuel de placer les données dans les dernières adresses de la mémoire. On codera l’algorithme en deux temps, d’abord symboliquement, puis on remplacera les symboles par des adresses mémoire effectives : – m = 1111 – n = 1110 – suite = 1000 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: suite 1000: 1001: .... n 1110: m 1111: READ READ LDA SUBA JIFOVR JUMP WRITE STOP WRITE STOP m n m n suite n m 6 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: .... 1111: READ READ LDA SUBA JIFOVR JUMP WRITE STOP WRITE STOP 1111 1110 1111 1110 0000 1000 1110 0000 1111 0000 Le programme occupe 10 octets (adresses 0000 à 1001), et les derniers octets de la mémoire (adresses 1010 à 1111) peuvent rester quelconques. 1.5 Codage de l’algorithme en binaire Il ne reste plus qu’à consulter la liste des instructions, section 3 page 11, pour remplacer le le nom symbolique de l’instruction ou mnémonique par le code binaire et définir ainsi ce qu’il faut enregistrer en mémoire : 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 0101 0101 0111 1010 0001 0010 0110 0000 0110 0000 1111 1110 1111 1110 0000 1000 1110 0000 1111 0000 Cette phase du travail et particulièrement délicate, car il est nécessaire de rester concentré et particulièrement attentif pour ne pas faire d’erreur. 1.6 Programmation de la machine et tests La programmation de cette machine est aussi délicate, car les erreurs sont difficiles à détecter à ce niveau. On se reportera à la section 4 pour un mode d’emploi complet de l’interface. Sur le Panneau de commande on procède pas à pas de la façon suivante : 1. clic sur le bouton "Remise à Zéro" ou sur les diodes INDX pour mettre l’adresse à zéro 2. clic sur les diodes IO pour définir la valeur binaire d’un octet 3. clic sur le bouton "Valider" l’octet à l’adresse INDX prend la valeur définie par les diodes, le registre INDX est automatiquement incrémenté 4. retour à l’étape 2 tant qu’il y des octets à définir 1.7 Test de l’algorithme Une fois la machine programmée on peut lancer l’exécution du programme, on procède pas à pas de la façon suivante : 7 1. clic sur le bouton "Remise à Zéro" puis sur le bouton "Exécuter" interruption diodes encadrées en rouge : attente du premier nombre 2. clic sur les diodes pour définir la valeur du premier nombre 3. clic sur le bouton "Valider" seconde interruption diodes encadrées en rouge : attente du second nombre 4. clic sur le bouton "Valider" affichage de la réponse interruption diodes encadrées en bleu : 5. clic sur le bouton "Continuer" interruption sur le STOP de fin de programme Pour vérifier la validité du programme, on fera au moins deux tests successifs, un en donnant le nombre le plus grand en premier, un autre en donnant le nombre le plus grand en second. 1.8 Résumé Dans cette section, nous avons vu comment concevoir un algorithme simple, comment l’écrire en langage machine, comment le coder en binaire, comment l’enregistrer dans la mémoire de la machine et enfin, comment le tester lors de l’exécution. On a constaté que la tâche à effectuer est difficile, car contrainte par le matériel utilisé, l’architecture de la machine, la taille de la mémoire et le jeux des instructions disponibles. On va maintenant décrire en détail cette machine. 2 Architecture de la machine L’ordinateur utilise un processeur à cinq registres et une mémoire vive de 16 octets. Un jeu de 16 instructions permet de le faire fonctionner. Le pilotage de la machine s’effectue à l’aide du Panneau de commande présenté page 18. Les instructions seront décrites de façon précise à section 3. Il est cependant nécessaire de connaître leur structure pour bien comprendre l’architecture de la machine décrite ci-dessous page 9. Chaque instruction est codée sur un octet placé dans la mémoire à l’adresse codée sur un demi-octet contenue dans le registre INDX : – le demi-octet partie haute de l’octet est le code de l’instruction il est donc possible de coder 16 instructions distinctes 8 – le demi-octet partie basse de l’octet est l’argument de l’instruction il représente une des 16 adresses de la mémoire 2.1 2.1.1 Registres du processeur Registres de contrôle Pour son fonctionnement le processeur de l’unité centrale utilise trois registres de contrôle 4 bits : – INDX le registre d’index aussi appelé compteur ordinal – DKOD le registre de décodage – ADRS le registre d’adresse Le registre d’index INDX, aussi appelé compteur ordinal, contient l’adresse du prochain octet de mémoire à traiter lors de l’exécution. Le bouton "Reset" permet de le mettre à zéro. Le bouton "Exécuter" l’incrémente automatiquement après exécution de chaque instruction, sauf dans le cas d’une instruction de saut. Le registre de décodage DKOD contient le demi-octet de poids fort de l’octet de mémoire en cours de traitement. Il sera interprété comme une instruction machine à zéro ou un argument. Le code de chaque instruction machine est précisé section 3. Le registre d’adresse ADRS contient le demi-octet de poids faible de l’octet de mémoire en cours de traitement. Il sera interprété comme argument de l’instruction, adresse d’un autre octet de la mémoire. Pour les instructions sans argument, il sera simplement ignoré. 9 2.1.2 Registre de calcul et bascule de débordement Les instructions de cette machine n’ont qu’un seul argument. Pour effectuer des opérations à deux arguments, comme l’addition ou la soustraction, le processeur dispose de registres supplémentaires : – A le registre de calcul 8 bits appelé accumulateur – OVERFLOW une bascule 1 bit qui signale le débordement du calcul On peut affecter au registre A la valeur d’un octet de la mémoire situé à l’adresse m (A ← m) ou affecter à la mémoire d’adresse m la valeur de l’accumulateur (m ← A) On peut aussi ajouter (A ← A + m) ou soustraire (A ← A - m) au registre A la valeur d’un octet de la mémoire situé à l’adresse m. Lors de ces opérations, un débordement peut se produire si le résultat est inférieur à 0 ou supérieur à 255. Dans ce cas, la bascule OVERFLOW est fixée à 1, et le résultat est donné modulo 256. Exemple d’addition : 255 + 3 donne 2 avec overflow car le reste de 258 par 256 est 2. Dans ce cas, l’overflow représente le quotient. Autrement dit, après une addition, le vrai résultat est OVERFLOW×256+A. L’overflow joue le rôle de retenue. Exemple de soustraction : 23 − 7 donne 252 avec overflow car le reste de −4 par 256 est 252. Dans ce cas, l’overflow représente l’opposé du quotient. Après une soustraction, le vrai résultat est A−OVERFLOW×256 On verra plus loin section 3 dans la description des différentes instructions machine, celles qui peuvent aussi provoquer un débordement. 2.1.3 Registre d’Entrée/Sortie Pour communiquer avec l’extérieur le contenu d’un registre est visualisé à l’aide de 8 diodes : – IO le registres d’Entrée/Sortie 8 bits Le registre IO (Input/Output) est utilisé pour les Entrées/Sorties. La valeur de ce registre est visible sur le Panneau de commande page 18, comme huit diodes bleues allumées (1) ou éteintes (0). En mode Programmation (diode rouge allumée) le bouton "Valider" permet de charger un programme en mémoire. En mode Exécution (diode verte allumée) toute opération d’Entrée/Sortie interrompt provisoirement le déroulement du programme après avoir encadré en rouge les 8 diode du registre IO : – Entrée ou lecture clics pour configurer l’état des diodes, puis bouton "Valider" 10 – Sortie ou affichage consulter l’état des diodes, puis bouton "Continuer" Le programme interrompu continue alors à se se dérouler normalement. 2.2 Mémoire Le registre INDX du processeur, aussi appelé compteur ordinal, permet d’adresser une mémoire de seize octets dans laquelle on peut enregistrer des valeurs numériques entières comprises entre 0 et 255 qui représentent, soit des données, soit les instructions d’un tout petit programme. Il n’y a aucune différence en mémoire entre les données et le programme. Seule l’interprétation qu’on en fait est significative. Comme le bouton "Reset" permet de remettre à zéro les registres de l’unité centrale, en particulier le registre INDX, il est nécessaire de placer le programme aux adresses 0000 et suivantes. Il est d’usage de placer les données aux adresses 1111 et précédentes. 2.3 Cycle d’exécution des instructions Le cycle d’exécution de chaque instruction se décompose en 3 phases : Phase 1 le registre INDX pointe sur l’adresse de l’octet mémoire à traiter Phase 2 l’octet est chargé dans les registres DKOD et ADRS pour décodage Phase 3 l’instruction correspondante est exécutée À la fin de chaque cycle une nouvelle valeur est affectée au registre INDX. – L’instruction STOP ne modifie pas le registre INDX ce qui revient à dire que le processeur s’arrête sur cette instruction. – Les instructions de saut sont décrites en détail ci-dessous. Ces instructions modifient le registre INDX d’une manière particulière. – À la fin des autres instructions le registre INDX est incrémenté. INDX ← INDX + 1 3 Langage machine Cet ordinateur peut être programmé à l’aide d’un jeu de seize instructions machine distinctes. 11 Chaque instruction machine est codée sur un octet dans la mémoire : – Le demi-octet de poids fort de cet octet définit l’instruction – Le demi-octet de poids faible est l’argument de l’instruction il fait référence à une adresse mémoire Pour être rigoureux dans la définition des instruction, on va utiliser les notations particulières suivantes : Les registres sont repérés par leurs noms INDX, A, IO et leur contenu est noté entre parenthèses (INDX), (A), (IO). adrs représente une adresse mémoire symbolique et (adrs) le contenu de la mémoire à cette adresse (un octet). Les seuls cas où on utilise un nom de registre ou une adresse mémoire directement (et non leur contenu) sont : – à gauche d’une affectation – en argument de l’instruction de saut Pour chaque instruction, on trouvera ci-dessous : – le code binaire et le mnémonique suivi éventuellement du nom anglais Exemple : 0111 : LDA Load A – une forme symbolique précisant l’action de l’instruction Exemple : A ← (adrs) – la définition détaillée du fonctionnement de l’instruction Exemple : Cette instruction affecte le registre A avec la valeur de l’octet pointé par adrs en mémoire. – cette définition sous une forme algorithmique précise A ← (adrs) Exemple : INDX ← (INDX) + 1 Lors de l’exécution du programme, les débordements éventuellement provoqués par les calculs seront signalés. De plus, une interruption du programme peut survenir lors de l’incrément du registre INDX si sa valeur dépasse 15 (sortie de la mémoire). Voici la liste complète des seize instructions machine disponibles : 3.0 0000 : STOP l’argument, partie basse de l’octet, est ignoré Stop Interruption du programme. L’incrément automatique du registre INDX est interrompu. INDX ← (INDX) 12 3.1 Jump if overflow 0001 : JIFOVR l’argument, partie basse de l’octet, est ignoré Saut_Si_Débordement Cette instruction ne fait rien, mais la suite dépend de l’état de la bascule OVERFLOW qui, lui-même, dépend de l’exécution de l’instruction précédente. Si OVERFLOW = 1 alors on saute l’instruction suivante et on continue à celle d’après INDX ← (INDX) + 2 sinon (OVERFLOW) = 0 alors on continue à l’instruction suivante INDX ← (INDX) + 1 fin_Si 3.2 0010 : JUMP Aller_à adrs Saut immédiat sans condition à l’adresse donnée comme argument INDX ← adrs Attention : ici, c’es bien la valeur adrs qui est affectée au registe INDX et non pas le contenu de la mémire à cette adresse. 3.3 0011 : JIFZ Jump if zero Saut_Si_Zéro (adrs) Cette instruction ne fait rien, mais la suite dépend de la valeur de l’octet pointé par adrs en mémoire. Si (adrs) = 0 alors on saute l’instruction suivante et on continue à celle d’après INDX ← (INDX) + 2 sinon (adrs) 6= 0 alors on continue à l’instruction suivante INDX ← (INDX) + 1 fin_Si 3.4 0100 : JIFNZ Jump if not zero Saut_Si_Non_Zéro (adrs) 13 Cette instruction ne fait rien, mais la suite dépend de la valeur de l’octet pointé par adrs en mémoire. Si (adrs) 6= 0 alors on saute l’instruction suivante et on continue à celle d’après INDX ← (INDX) + 2 sinon (adrs) = 0 alors on continue à l’instruction suivante INDX ← (INDX) + 1 fin_Si 3.5 0101 : READ Lire adrs Cette instruction interrompt le déroulement du programme dans l’attente d’une donnée fournie par l’utilisateur. Sur le Panneau de commande les diodes sont éteintes et encadrées en rouge. L’utilisateur peut modifier leur état par des clics. Comme les diodes sont synchronisées avec le registre IO l’utilisateur définit ainsi son état. Le bouton "Valider" permet alors d’affecter cette valeur à la mémoire dont l’adresse est argument de l’instruction, avant de continuer IO ← 0 ...interruption... puis "Valider" adrs ← (IO) INDX ← (INDX) + 1 3.6 0110 : WRITE Afficher (adrs) Cette instruction interrompt le déroulement du programme pour laisser à l’utilisateur le temps de lire une réponse. Le contenu de la mémoire dont l’adresse est argument de l’instruction est affecté au registre IO qui est synchronisé avec les diodes. Celle-ci sont alors encadrées en rouge, puis l’interruption se produit. Dès que l’utilisateur a consulté l’état des diodes, le bouton "Continuer" permet reprendre l’exécution du programme. IO ← (adrs) ...interruption... puis "Continuer" INDX ← (INDX) + 1 14 3.7 0111 : LDA Load A A ← (adrs) Cette instruction affecte le registre A avec la valeur de l’octet pointé par adrs en mémoire. A ← (adrs) INDX ← (INDX) + 1 3.8 1000 : STA Store A adrs ← (A) Cette instruction affecte l’octet de la mémoire pointé par adrs avec la valeur du registre A . adrs ← (A) INDX ← (INDX) + 1 3.9 1001 : ADDA Add A A ← (A) + (adrs) Cette instruction ajoute à l’accumulateur A la valeur de l’octet pointé par adrs en mémoire. Un débordement éventuel est signalé. Si (A) + (adrs) > 255 alors débordement OVERFLOW ← 1 A ← (A) + (adrs) - 256 somme modulo 256 sinon OVERFLOW ← 0 A ← (A) + (adrs) fin_Si INDX ← (INDX) + 1 3.10 1010 : SUBA Subtract A A ← (A) - (adrs) Cette instruction retranche à l’accumulateur A la valeur de l’octet pointé par adrs en mémoire. Un débordement éventuel est signalé. 15 Si (A) - (adrs) < 0 alors OVERFLOW ← 1 A ← (A) - (adrs) + 256 sinon OVERFLOW ← 0 A ← (A) - (adrs) fin_Si INDX ← (INDX) + 1 3.11 1011 : INC débordement somme modulo 256 Increment adrs ← (adrs) + 1 Cette instruction incrémente l’octet pointé par adrs en mémoire. Un débordement éventuel est signalé. Si (adrs) = 255 alors débordement OVERFLOW ← 1 adrs ← 0 sinon OVERFLOW ← 0 adrs ← (adrs) + 1 fin_Si INDX ← (INDX) + 1 3.12 1100 : DEC Decrement adrs ← (adrs) - 1 Cette instruction décrémente l’octet pointé par adrs en mémoire. Un débordement éventuel est signalé. Si adrs = 0 alors débordement OVERFLOW ← 1 adrs ← 255 sinon OVERFLOW ← 0 adrs ← (adrs) - 1 fin_Si INDX ← (INDX) + 1 3.13 1101 : LSHIFT Left shift adrs ← (adrs) × 2 16 Cette instruction décale vers la gauche les bits de l’octet pointé par adrs en mémoire. Cette opération correspond à une multiplication par 2 de la valeur. Un débordement éventuel est signalé si le bit de poids fort est à 1 et qu’il disparaît lors du décalage (c’est à dire pour une valeur supérieure à 127). (adrs) > 127 alors débordement OVERFLOW ← 1 adrs ← (adrs) × 2 - 256 sinon OVERFLOW ← 0 adrs ← (adrs) × 2 fin_Si INDX ← (INDX) + 1 Si 3.14 1110 : RSHIFT Right shift adrs ← (adrs) ÷ 2 Cette instruction décale vers la droite les bits de l’octet pointé par adrs en mémoire. Cette opération correspond à une division entière par 2 de la valeur. Un débordement éventuel est signalé si le bit de poids faible est à 1 et qu’il disparaît lors du décalage (c’est à dire si on divise une valeur impaire). Si (adrs) est impair alors OVERFLOW ← 1 adrs ← ((adrs) - 1) ÷ 2 sinon OVERFLOW ← 0 adrs ← (adrs) ÷ 2 fin_Si INDX ← (INDX) + 1 3.15 1111 : CMPL débordement Complement adrs ← (adrs) Cette instruction effectue le complément bit à bit de l’octet pointé par adrs en mémoire. adrs ← 255 - (adrs) INDX ← (INDX) + 1 17 4 4.1 Mode d’emploi de l’application Programmation En mode "Programme", on voit sur le panneau de commande les quatre diodes du demi-octet du registre INDX encadrées en vert. Clic sur le bouton "Remise à Zéro" éteint toute les diodes et permet de sélectionner la première adresse de la mémoire. Clic sur les diodes du registre INDX permet de sélectionner une adresse quelconque. Les huit diodes du registre IO encadrées en vert permettent, de même, de définir la valeur d’un octet. Clic sur le bouton "Valider" pour charger dans la mémoire à l’adresse définie par le registre INDX la valeur définie par le registre IO. Le registre INDX est automatiquement incrémenté à la fin de cet enregistrement. On peut suivre sur le panneau "Mémoire vive" le bon déroulement des opérations. 4.2 Exécution Une fois le programme enregistré en mémoire, un clic sur la diode de mode permet de passer en mode "Exécution", la diode de mode devient rouge : Le bouton "•" en haut à droite du panneau de commande est le bouton "Reset" de la machine. Il est toujour disponible et permet éventuellement d’interrompre un programme qui boucle. 18 Lors du passage en mode "Exécution", le panneau de l’unité centrale apparaît sous le panneau de commande : Pour exécuter un programme, on fixe l’adresse de départ à l’aide des diodes du registre INDX ou avec le bouton "Remise à Zéro". Il y a ensuite deux façons d’exécuter le code en mémoire : – clic sur le bouton "Exécuter" lance un exécution automatique rapide Cette exécution peut être interrompue : – par une lecture... clic sur le bouton "Valider" pour continuer – par un affichage... clic sur le bouton "Continuer" – par un STOP de fin de programme – par une erreur de sortie de la mémoire – par un clic sur le bouton "•" de reset. – clic sur le bouton "Pas à Pas" lance l’exécution d’une instruction : – cette instruction s’exécute phase par phase – un clic sur "Pas à Pas" est nécessaire à chaque phase pour continuer – des messages sont affichés en bas de l’écran pour décrire le fonctionnement de l’unité centrale 4.3 Assembleur Pour des programmes un peu complexes, la programmation en binaire sur le panneau de commande est un peu fastidieuse. On propose donc une façon de programmer plus souple grâce à l’assembleur. en haut à droite Pour pouvoir utiliser l’assembleur : clic sur le bouton de la fenêtre. 19 Dans les exemples présentés à la section 5 on utilise dans les paragraphes « Écriture de l’algorithme en langage machine » un langage symbolique simple dans lequel les adresses de la mémoire sont simplement étiquetées symboliquement, mais pas calculée explicitement. Il est possible de programmer la machine en utilisant ce langage. En haut de la fenêtre on voit deux boutons : "Dump" et "Assembleur". il ouvrent une fenêtre d’édition de texte qui affiche le contenu de la mémoire de façon plus lisible : – "Dump" affiche le contenu de la mémoire avec chaque ligne sous la forme <adresse>: <instruction> <argument> avec : – <adresse>: un entier entre 0 et 15 en binaire ou en décimal – <instruction> un mnémonique ou un entier entre 0 et 15 – <argument> un entier entre 0 et 15 en binaire ou en décimal – "Assembleur" affiche le contenu de la mémoire exprimé dans le langage symbolique « Assembleur » de la machine Inversement, on peut écrire dans fenêtre d’édition de texte un programme indifféremment sous formes numérique ou symbolique. Le bouton "Enregistrer" place les données en mémoire si elles sont correctes. En cas d’erreur, des messages sont affichés pour faciliter la mise au point du programme. Lorsque la fenêtre d’édition est ouverte, un menu propose de charger automatiquement les programmes des exercices présenté section 5. À titre d’exemple utilisons l’exercice « Multiplier deux nombres » présenté page 29 pour préciser les différentes formes sous lesquelles le programme peut être écrit : Le programme peut être entré en mémoire ainsi : 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: READ READ RSHIFT JIFOVR JUMP ADDA LSHIFT JIFZ JUMP STA WRITE STOP 1111 1110 1110 0000 0110 1111 1111 1110 0010 1101 1101 0000 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 20 0101 0101 1110 0001 0010 1001 1101 0011 0010 1000 0110 0000 1111 1110 1110 0000 0110 1111 1111 1110 0010 1101 1101 0000 0: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 5 5 14 1 2 9 13 3 2 8 6 0 15 14 14 0 6 15 15 14 2 13 13 0 Avec cette syntaxe les trois champs sont toujours obligatoires à chaque ligne, mais on peut écrire les nombres, soit en binaire (avec 4 chiffres obligatoires) soit en décimal (0 à 15). En assembleur, on l’écrira plus simplement : READ READ debut RSHIFT JIFOVR JUMP ADDA suite LSHIFT JIFZ JUMP STA WRITE STOP m n n suite m m n debut p p Les adresses symboliques seront automatiquement remplacées par les adresses numériques effectives. 5 Autres exemples Les exemples suivants sont classés par ordre de difficulté croissante. Le premier exemple « Complément bit à bit » page 21 est élémentaire et permet de se familiariser avec la machine. Le dernier exemple « Somme des N premiers entiers » page 32 est un des plus complexe qu’il est possible de réaliser sur cette machine, compte tenu de la faible capacité de sa mémoire. 5.1 Complémenter bit à bit Le problème Lire un octet, afficher son complément bit à bit. Algorithme de résolution du problème Le problème ne présente aucune difficulté : Lire M Complémenter M Afficher M Stop 21 Adaptation de l’algorithme au processeur et codage en binaire On peut effectuer une traduction immédiate de l’algorithme et le coder en binaire ensuite : 0000: 0001: 0010: 0011: READ CMPL WRITE STOP 1111 1111 1111 0000 0000: 0001: 0010: 0011: 0101 1111 0110 0000 0100 0100 0100 0000 Le test du programme ne présente pas de difficulté. 5.2 Clignoter les diodes N fois Le problème Entrer un entier N, faire clignoter les diodes N fois Algorithme de résolution du problème Lire N M ← 0 Lire N Tant_Que N > 0 faire M ← Complément(M) Afficher M N ← N - 1 Fin_Tant_Que Stop Adaptation de l’algorithme au processeur M ← A Lire N BOUCLE Complémenter M Afficher M N ← N - 1 Saut_Si_Zéro N Aller_à BOUCLE Stop zéro dans M si N=0 c’est fini si N6=0 on continue Écriture de l’algorithme en langage machine Codage d’abord symbolique, puis avec adresses mémoire effectives : 22 0000: STA m 0001: READ n 0010: boucle CMPL m 0011: WRITE m 0100: DEC n 0101: JIFZ n 0110: JUMP boucle 0111: STOP 1000: m 1001: n 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: STA READ CMPL WRITE DEC JIFZ JUMP STOP .... .... 1000 1001 1000 1000 1001 1001 0010 0000 Codage de l’algorithme en binaire 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000 0101 1111 0110 1100 0011 0010 0000 1000 1001 1000 1000 1001 1001 0010 0000 Tests Après avoir entré un entier à l’aide des diodes IO puis clic sur le bouton "Valider" les diodes IO seront successivement allumées puis éteintes après chaque clic sur le bouton "Continuer" jusqu’à l’interruption du programme. 5.3 Additioner de deux entiers Le problème Entrer deux entiers M et N , afficher M + N Algorithme de résolution du problème La somme de deux entiers codés chacun sur un octet (c’est à dire compris entre 0 et 255) peut dépasser 255 et occasionner un débordement. La réponse sera donc donnée par affichage de deux octets successivement : 1. R la retenue : 0 si la somme est inférieure à 256 et 1 sinon 2. S la somme modulo 256 (sans la retenue) 23 R ← 0 Lire M Lire N S ← M + N Si overflow faire R ← R + 1 Fin_Si Afficher R Afficher S Fin Adaptation de l’algorithme au processeur R ← A Lire M A ← M Lire N A ← A + N Saut_Si_Overflow Aller_à SUITE R ← R + 1 SUITE Afficher R S ← A Afficher S Stop zéro dans la retenue R somme si overflow, retenue sinon affichage retenue somme Écriture de l’algorithme en langage machine Codage d’abord symbolique, puis avec adresses mémoire effectives : 24 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: STA r READ m LDA m READ n ADDA n JIFOVR JUMP suite INC r suite WRITE r STA s WRITE s STOP r s n m 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: STA READ LDA READ ADDA JIFOVR JUMP INC WRITE STA WRITE STOP .... .... .... .... 1100 1110 1110 1111 1111 0000 1000 1100 1100 1101 1101 0000 Codage de l’algorithme en binaire 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1000 0101 0111 0101 1001 0001 0010 1011 0110 1000 0110 0000 1100 1110 1110 1111 1111 0000 1000 1100 1100 1101 1101 0000 Tests Pour vérifier le calcul de la retenue, on effectuera au moins deux tests : – un avec une somme inférieure à 256 – un avec une somme supérieure à 256 5.4 Tripler un nombre sans retenue Le problème Entrer un entiers N , afficher 3 × N 25 Dans ce premier exemple simple, on donnera un nombre N inférieur à 86, de façon à ce que son triple soit inférieur à 256. En effet dans cet exemple, on ignore les débordements éventuels. On trouvera ci-dessous sous-section 5.5 le même problème dans lequel les retenues sont prises en compte dans les calculs. Algorithme de résolution du problème Pour effectuer des multiplications en binaire, on effectue des décalages bit à bit et des additions. En effet un décalage vers la gauche des bits d’un octet correspond à une multiplication par 2. La multiplication de N par 3 sera effectuée comme la somme de N avec 2 × N , c’est à dire N décalé vers la gauche : Lire N A ← N N ← N × 2 A ← A + N N ← A Afficher N Fin Écriture de l’algorithme en langage machine 0000: 0001: 0010: 0011: 0100: 0101: 0110: .... 1111: n READ LDA LSHIFT ADDA STA WRITE STOP n n n n n n 0000: 0001: 0010: 0011: 0100: 0101: 0110: .... 1111: Codage de l’algorithme en binaire 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0101 0111 1101 1001 1000 0110 0000 26 1111 1111 1111 1111 1111 1111 0000 READ LDA LSHIFT ADDA STA WRITE STOP .... 1111 1111 1111 1111 1111 1111 0000 5.5 Tripler un nombre avec retenue Le problème Entrer un entiers N , afficher 3 × N Dans cet exemple les retenues sont prises en compte dans les calculs. Algorithme de résolution du problème On teste les débordements éventuels après les opérations de décalage à gauche et d’addition. On utilise une mémoire R pour les retenues éventuelles. En fin de programme on affiche successivement la mémoire de retenue puis le produit. R ← 0 Lire N A ← N N ← N × 2 Si overflow faire R ← R + 1 Fin_Si A ← A + N Si overflow faire R ← R + 1 Fin_Si Afficher R N ← A Afficher N Fin Adaptation de l’algorithme au processeur On utilise la variable R pour cumuler les retenus éventuelles. Le produit est effectué dans l’accumulateur A. On affiche ensuite successivement R qui est la partie haute de la réponse et A la partie basse, c’est a dire que le triple de N est égal à 256 × R + A. 27 R ← A Lire N A ← N N ← N × 2 Saut_Si_Overflow Aller_à SUITE R ← R + 1 SUITE A ← A + N Saut_Si_Overflow Aller_à FIN R ← R + 1 FIN Afficher R N ← A Afficher N Stop zéro dans la retenue R décalage à gauche si overflow, retenue sinon somme si overflow, retenue sinon affichage retenue triple Écriture de l’algorithme en langage machine Codage d’abord symbolique, puis avec adresses mémoire effectives : 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: STA READ LDA LSHIFT JIFOVR JUMP INC suite ADDA JIFOVR JUMP INC fin WRITE STA WRITE r n r n n n suite r n fin r r n n 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: STA READ LDA LSHIFT JIFOVR JUMP INC ADDA JIFOVR JUMP INC WRITE STA WRITE .... .... 1110 1111 1111 1111 0000 0111 1110 1111 0000 1011 1110 1110 1111 1111 On remarquera une petite astuce de programmation. En raison du manque de place en mémoire l’instruction STOP de fin de programme a été omise. L’octet d’adresse 1110: qui suit l’instruction WRITE du dernier affichage est utilisé pour calculer la retenue. Cet octet est initialisé à 0 à la première instruction du programme et peut être incrémenté une ou deux fois selon les cas. Sa valeur ne dépassera donc pas 2 c’est à dire 0000 0010. 28 On remarque donc que l’octet d’adresse 1110 a toujours dans son demioctet en partie haute la valeur 0000. le décodage de cette valeur comme instruction est interprété comme un STOP. Le programme s’arrêtera donc. Cette astuce montre bien que la mémoire contient indifféremment des données ou des instructions, et que seule l’interprétation qui en est faite à un instant donné est significative. Codage de l’algorithme en binaire 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1000 0101 0111 1101 0001 0010 1011 1001 0001 0010 1011 0110 1000 0110 1110 1111 1111 1111 0000 0111 1110 1111 0000 1011 1110 1110 1111 1111 Tests Pour vérifier la validité du résultat, on testera avec au moins deux exemples, un sans retenue et un avec retenue. Voici quelques jeux de valeurs possibles : • N = 0000 0101 (5) → 0000 0000/0000 1111 (0 × 256 + 15 = 15) • N = 1111 1111 (255) → 0000 0010/1111 1101 (2 × 256 + 253 = 765) • N = 1000 0000 (128) → 0000 0001/1000 0000 (1 × 256 + 128 = 184) 5.6 Multiplier deux nombres Nous avons vu précédemment comment multiplier par 3. On peut aussi multiplier deux nombres quelconques avec une méthode similaire. Le problème Entrer deux entier M et N, afficher le produit M × N . 29 On procède par étape. Le multiplicande est multiplié par 2 à chaque étape, et, simultanément on effectue la division entière du multiplicateur par 2. On accumule les valeurs successives du multiplicande à chaque fois que le multiplicateur est impair. Exemple : M × N = 123 × 70 = 8610 multiplicande multiplicateur reste M ×2 N ÷ 2 N modulo 2 somme partielle 123 70 0 0 × 123) + 0 246 35 1 1 × 246 + 0 492 17 1 1 × 492 + 246 984 8 0 0 × 984 + 738 1968 4 0 0 × 1968 + 738 3936 2 0 0 × 3936 + 738 7872 1 1 1 × 7872 + 738 123 × 70 = 8610 Algorithme de résolution du problème A ← 0 Lire M Lire N Tant_Que N > 0 faire M ← M × 2 Si N impair faire A ← A + M Fin_Si N ← N ÷ 2 Fin_Tant_Que Afficher A Stop Adaptation de l’algorithme au processeur Une adaptation de l’algorithme est vraiment nécessaire, car on ne dispose de condition sur la parité. Mais, la valeur d’un octet est paire si son bit de droite est 0, et sa valeur est impaire si son bit de droite est 1. Si on effectue un décalage à droite bit à bit, il y a débordement dans le cas où le bit de droite est 1, et donc le test de débordement informe sur la parité. Le test d’arrêt de la boucle portera sur la nullité du multiplicateur. On peut réécrire l’algorithme ainsi : 30 Lire M Lire N DEBUT N ← N ÷ 2 Saut_Si_Overflow Aller_à SUITE A ← A + M SUITE M ← M × 2 Saut_Si_Zéro N Aller_à DEBUT P ← A Afficher P Stop On remarque que le résultat est accumulé dans le registre A et qu’il ne peut donc pas dépasser la valeur 256. La gestion des débordements et des retenues dans le calcul n’est hélas pas possible dans une mémoire aussi petite. Écriture de l’algorithme en langage machine Codage d’abord symbolique, puis avec adresses mémoire effectives : 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: READ READ debut RSHIFT JIFOVR JUMP ADDA suite LSHIFT JIFZ JUMP STA WRITE STOP p n m m n n suite m m n debut p p ... ... ... 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: READ READ RSHIFT JIFOVR JUMP ADDA LSHIFT JIFZ JUMP STA WRITE STOP .... .... .... .... 1111 1110 1110 0000 0110 1111 1111 1110 0010 1101 1101 0000 Cette multiplication ne donnera un résultat correct que si le produit des deux nombres est inférieur à 256. Dans le cas contraire le résultat sera en réalité affiché modulo 256. 31 Codage de l’algorithme en binaire 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 0101 0101 1110 0001 0010 1001 1101 0011 0010 1000 0110 0000 1111 1110 1110 0000 0110 1111 1111 1110 0010 1101 1101 0000 Tests Pour vérifier la validité du programme, on utilisera de petits nombres pour ne pas dépasser les capacités de calcul. Voici quelques jeux de valeurs possibles : • M = 0000 0101 (5) N = 0000 0110 (6) • M = 0111 1111 (127) N = 0000 0010 (2) • M = 0000 1111 (15) N = 0000 0111 (7) → M × N = 0001 1110 (30) → M × N = 1111 1110 (254) → M × N = 0110 1001 (105) Par contre on ne sera pas surpris par : • M = 0001 0000 (16) N = 0001 0000 (16) → M × N = 0000 0000 (256) Et on pourra vérifier en calculant modulo 256 que : • M = 0101 0101 (85) N = 1010 1010 (170) → M × N = 0111 0010 (114) 5.7 Somme des N premiers entiers Cet exercice est un des plus complexe qu’il est possible de réaliser sur cette machine, compte tenu de la faible capacité de sa mémoire. On va procéder pas à pas, comme dans l’exemple de la section 1, pour concevoir l’algorithme et écrire le programme. Le problème Lire un nombre N. Afficher la somme des entiers de 1 à N. Algorithme de résolution du problème Voici un algorithme pour résoudre le problème : 32 Lire N A ← 0 Tant_Que N > 0 A ← A + N N ← N - 1 Fin_Tant_Que Afficher A Stop faire Évidement, cet algorithmique n’est pas adapté à notre machine. Il faut prendre en compte certaines contraintes : – A ← 0 est inutile, car effectué lors du "Reset" – Afficher A n’est pas possible, il faut d’abord affecter la valeur en mémoire avant de l’afficher Mais surtout, la somme des entiers de 1 à N (avec N de 0 à 255), peut être un entier supérieure à 255. Le résultat doit être donné sur deux octets. La somme doit être effectuée en tenant compte des retenues (débordements) dans le calcul. Adaptation de l’algorithme au processeur Dans ce qui suit, la somme sera effectuée dans l’accumulateur A. Lors des additions, si des débordements ont lieu, les retenues seront accumulées dans la variable H. Le registre A étant à zéro au départ, il sera affectée à la variable H pour l’initialiser aussi à zéro avant de commencer le calcul. L’affichage de la réponse se fera par les affichages successifs de la variable H, partie haute de la réponse et du registre A, partie basse de la réponse. Voici un algorithme, assorti de quelques commentaires, qui tient compte des contraintes imposées par le processeur en utilisant les instructions symboliques proposées dans la section 3 : 33 R ← A zéro dans R Lire N DEBUT Saut_Si_Non_Zéro N Aller_à FIN si N=0 A ← A + N si N6=0 on accumule Saut_Si_Débordement Aller_à SUITE pas de retenue R ← R + 1 retenue dans R SUITE N ← N - 1 on décrémente N Aller_à DEBUT et on recommence FIN Afficher R réponse partie haute R ← A Afficher R réponse partie basse Stop Tel qu’il est, ce programme peut facilement être codé en langage machine. Écriture de l’algorithme en langage machine Codage d’abord symbolique, puis avec adresses mémoire effectives : 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: STA READ debut JIFNZ JUMP ADDA JIFOVR JUMP INC suite DEC JUMP fin WRITE STA WRITE STOP r n r n n fin n suite r n debut r r r 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1110: 1111: STA READ JIFNZ JUMP ADDA JIFOVR JUMP INC DEC JUMP WRITE STA WRITE STOP .... .... 1110 1111 1111 1010 1111 0000 1000 1110 1111 0010 1110 1110 1110 0000 Codage de l’algorithme en binaire Il ne reste plus qu’à remplacer les mnémoniques par le code binaire pour définir ce qu’il faut enregistrer en mémoire : 34 0000: 0001: 0010: 0011: 0100: 0101: 0110: 0111: 1000: 1001: 1010: 1011: 1100: 1101: 1000 0101 0100 0010 1001 0001 0010 1011 1100 0010 0110 1000 0110 0000 1110 1111 1111 1010 1111 0000 1000 1110 1111 0010 1110 1110 1110 0000 Tests On pourra tester sur quelques jeux de valeurs, par exemple : • N = 0000 0011 (3) → 0000 0000/0000 0110 (0 × 256 + 6 = 6) • N = 1111 1111 (255) → 0111 1111/1000 0000 (127 × 256 + 128 = 32640) • N = 0101 0101 (85) → 0000 1110/0100 0111 (14 × 256 + 71 = 3655) • N = 0101 1010 (90) → 0000 1111/1111 1111 (15 × 256 + 255 = 4095) 35