Université de Bordeaux Collège Sciences et Technologies ANNÉE UNIVERSITAIRE 2019 -- 2020 SESSION 1 D'AUTOMNE ́ Parcours / Étape : LSTS / L2 Code UE : 4TIN304U Épreuve : Architecture des ordinateurs * QUESTION 1 [120 points] ============ * Question 1.1 [20 points] Sur sa sortie S, le multiplexeur doit renvoyer A si C = 0 et B si C = 1. Son équation est donc : S = A./C + B.C ("/C" se lit "C barre", donc "non C") En termes de câblage, on a donc : S = OR (AND (A, NOT (C)), AND (B, C)). Appelons "MUX" le circuit ci-dessus. Pour créer "MUX4", il faut simplement mettre en parallèle quatre "MUX", un pour chaque paire de bits a_{i} et b_{i}, et qui partagent le même signal de commande C : i = { 0, 1, 2, 3 } MUX{i}:C = C MUX{i}:A = a_{i} MUX{i}:B = b_{i} s_{i} = MUX{i}:S * Question 1.2 [20 points] Les vecteurs de vérité des fonctions S et C sont les suivants : S : "0 1 1 0" = XOR (A, B) C : "0 0 0 1" = AND (A, B) * Question 1.3 [20 points] On cherche à réaliser l'addition : [a_3 a_2 a_1 a_0] + [0 0 0 1] = [x_3 x_2 x_1 x_0] Il faut donc additionner 1 à a_0, puis propager la retenue éventuelle au bit de poids immédiatement supérieur, et ainsi de suite. On a donc : i = { 1, 2, 3 } HA0:A = a_0 HA0:B = 1 x_0 = HA0:S # = NOT (a_0), par simplification du XOR (a_0, 1) c_0 = HA0:C # = a_0, par simplification du AND (a_0, 1) HA{i}:A = a_{i} HA{i}:B = c_{i-1} # Propagation de la retenue x_{i} = HA{i}:S c_{i} = HA{i}:C * Question 1.4 [20 points] Pour ne pas générer de dérive du signal issu de l'horloge H, il faut toujours brancher directement celle-ci sur les entrées des bascules D que l'on utilise. Il s'agit ici d'incrémenter un mot binaire de 4 bits, stocké dans 4 bascules D, et de vérifier si c'est ce mot incrémenté qui doit être replacé dans les bascules D, ou bien le mot "0 0 0 0". Pour effectuer cette sélection, on utilise un multiplexeur MUX4 : i = { 0, 1, 2, 3 } D{i}:C = H # Horloges des bascules D sans dérive INC:a_{i] = D{i}:Q # Sorties des bascules D sur l'incrémenteur MUX4:A = INC:X # Valeur incrémentée sur l'entrée A du multiplexeur MUX4:b_{i} = 0 # Valeur 0 sur l'entrée B du multiplexeur MUX4:C = Z # Le bit Z contrôle le multiplexeur D{i}:D = MUX4:s_{i} # Sortie du multiplexeur sur les 4 bascules D Dans ce cas précis, on peut simplifier le multiplexeur sous la forme de quatre portes AND en parallèle, passantes quand Z est à 0. On peut l'écrire sous la forme : D{i}:D = AND (INC:x_{i}, NOT (Z)) # Valeur incrémentée si /Z, sinon 0 * Question 1.5 [10 points] Comme pour la question précédente, pour ne pas générer de dérive de l'horloge, il faut toujours brancher directement celle-ci sur les entrées des bascules D que l'on utilise. Il s'agit ici d'alimenter quatre bascules D, soit avec leur valeur actuelle, soit avec la nouvelle valeur D fournie en entrée. On reboucle donc ici encore avec un multiplexeur MUX4 pour fournir aux bascules D la bonne entrée : i = { 0, 1, 2, 3 } D{i}:C = H # Horloges des bascules D sans dérive MUX4:a_{i} = D{i}:Q # Valeur mémorisée sur l'entrée A du multiplexeur MUX4:b_{i} = d_{i} # Valeur d'entrée D sur l'entrée B du multiplexeur MUX4:C = L # Le bit L contrôle le multiplexeur D{i}:D = MUX4:s_{i} # Sortie du multiplexeur sur les 4 bascules D Q_{i} = D{i}:Q # La sortie du registre est la valeur des bascules * Question 1.6 [10 points] Le vecteur de vérité qui indique quand deux bits A et B sont égaux est le suivant : "1 0 0 1" = NXOR ("not xor", ce qui est logique puisque "xor" indique la différence). Les deux mots de 4 bits A et B sont égaux si tous leurs bits sont égaux à la fois, ce qui se teste au moyen d'une porte AND globale. On a donc : i = { 0, 1, 2, 3 } z_{i} = NXOR (a_{i}, b_{i}) Z = AND (z_0, z_1, z_2, z_3) * Question 1.7 [20 points] On a besoin d'un registre pour mémoriser la valeur maximum M du compteur, dont la mise à jour est contrôlée par le bit L. Le compteur peut être remis à zéro soit lorsqu'il a atteint cette valeur M, ce que l'on vérifiera par un comparateur EQU, mais aussi sur activation du bit d'entrée Z ; il faudra donc faire un OR entre ces deux conditions pour activer l'entrée Z du compteur. Comme pour les questions précédentes, pour ne pas générer de dérive de l'horloge, il faut toujours brancher directement celle-ci sur les entrées des bascules D que l'on utilise, au sein du REG et du CPT. On a donc : REG:D = M REG:C = H REG:L = L CPT:Z = OR (EQU:Z, Z) # Remise à zéro du compteur sur max ou Z CPT:C = H EQU:A = REG:Q EQU:B = CPT:N N = CPT:N * QUESTION 2 [110 points] ============ * Question 2.1 [20 points] La première ligne concerne les cas de RRMOVL, et des OPL sans valeur immédiate (puisqu'ici la valeur du registre source rA n'est pas égale à RNONE). Dans le premier cas, le contenu du registre source rA est transmis à l'UAL, où il est additionné à O (passé dans valB), ce qui fait que le contenu de rA sera bien écrit dans rB, en tant que valE = valA + 0, après le passage obligé à travers l'UAL. Dans le second cas, on utilise l'UAL pour effectuer l'opération arithmétique et logique entre rA et rB. La deuxième ligne concerne les instructions possédant une valeur immédiate valC. Dans le cas de IRMOVL, on additionne cette valeur à 0 pour la transférer dans rB, en tant que valE = valC + 0, à l'image de ce que fait RRMOVL. Dans le cas de RMMOVL et MRMOVL, il s'agit de calculer l'adresse mémoire de la forme "valC(%rB)", sachant que si rB vaut RNONE (comme dans le cas de "rmmovl %eax,0x100"), c'est une valeur 0 qui sera extraite de la banque de registres en tant que valB. Le dernier cas, celui d'OPL, est en fait celui des "IOPL", car ici c'est une OPL dont rA vaut RNONE (l'autre cas ayant été traité dans la ligne précédente). On effectue alors bien l'opération arithmétique ou logique entre valC et valB. La troisième ligne concerne les instructions qui ajoutent un élément à la pile. Pour cela, il faut calculer la nouvelle valeur du sommet de pile, qui est égale à %esp - 4. La constante -4 est donc passée en tant que valeur valA, alors que %esp est passé dans valB. La quatrième ligne concerne les instructions qui retirent un élément de la pile. La nouvelle valeur du sommet de pile est alors égale à %esp + 4. Pour la calculer, on fournit +4 dans valA, et %esp dans valB. * Question 2.2 [20 points] L'instruction "CMPL rA,rB" ressemble fortement à "SUBL" : elle effectue la soustraction de rA à rB, en modifiant les bits d'état (Z, S et O), mais sans modifier rB. Elle ne positionne donc pas dstE. Elle n'a pas besoin de la mémoire (dstM et autres). Elle n'a besoin que de l'octet de registres, mais pas de valC. Pour effectuer la soustraction, soit on assure, dans le codage de l'instruction, que son ifun sera toujours celui de ALUSUB (mais il faut alors le mentionner explicitement dans la copie), soit on force l'exécution d'une soustraction par l'UAL lorsque le icode est CMPL (c'est cette solution qui est montrée ici). * Question 2.3 [20 points] Les instructions "INCL r" et "DECL r" sont analogues à "IADDL 1,r" et "ISUBL 1,r", à la différence que l'on n'a pas besoin de fournir la valeur immédiate dans valC (on économise donc 4 octets dans la taille de l'instruction). On n'utilisera donc pas valC. Cette instruction n'utilise pas non plus la mémoire. Dans le codage de l'instruction, on peut choisir d'utiliser rA ou rB comme numéro de registre concerné. Comme ce registre est modifié par l'instruction, il est plus simple de considérer que le registre est rB, comme dans les autres instructions arithmétiques et logiques dont le registre de destination est rB. Sinon, si l'on prend rA, cela oblige à complexifier les multiplexeurs de l'architecture, en faisant que la valeur rA serve de numéro de registre de destination pour dstE. On peut choisir ou non de modifier les codes de condition (ce n'était pas spécifié dans le sujet). Dans cette correction, on le fera, par cohérence avec IADDL et ISUBL. Le code qui a été ajouté concerne les opcodes CMPL, DECL et INCL. ################ Fetch Stage ################################### # Does fetched instruction require a regid byte? bool need_regids = icode in { RRMOVL, OPL, PUSHL, POPL, IRMOVL, RMMOVL, MRMOVL, CMPL, DECL, INCL }; # Does fetched instruction require a constant word? bool need_valC = icode in { IRMOVL, RMMOVL, MRMOVL, JXX, CALL } || ((icode == OPL) && (rA == RNONE)); # List of all valid instructions bool instr_valid = icode in { NOP, HALT, RRMOVL, IRMOVL, RMMOVL, MRMOVL, OPL, JXX, CALL, RET, PUSHL, POPL, CMPL, DECL, INCL }; ################ Decode Stage ################################### ## What register should be used as the A source? int srcA = [ icode in { RRMOVL, RMMOVL, OPL, PUSHL, CMPL } : rA; icode in { POPL, RET } : RESP; 1 : RNONE; # Don't need register ]; ## What register should be used as the B source? int srcB = [ icode in { RMMOVL, MRMOVL, OPL, CMPL, DECL, INCL } : rB; icode in { PUSHL, POPL, CALL, RET } : RESP; 1 : RNONE; # Don't need register ]; ## What register should be used as the E destination? int dstE = [ icode in { RRMOVL, IRMOVL, OPL, DECL, INCL } : rB; icode in { PUSHL, POPL, CALL, RET } : RESP; 1 : RNONE; # Don't need register ]; ## What register should be used as the M destination? int dstM = [ icode in { MRMOVL, POPL } : rA; 1 : RNONE; # Don't need register ]; ################ Execute Stage ################################### ## Select input A to ALU int aluA = [ icode in { RRMOVL, CMPL } || ((icode == OPL) && (rA != RNONE)) : valA; icode in { IRMOVL, RMMOVL, MRMOVL, OPL } : valC; icode in { CALL, PUSHL } : -4; icode in { RET, POPL } : 4; icode in { DECL } : -1; icode in { INCL } : 1; # Other instructions don't need ALU ]; ## Select input B to ALU int aluB = [ icode in { RMMOVL, MRMOVL, OPL, CALL, PUSHL, RET, POPL, CMPL, DECL, INCL } : valB; icode in { RRMOVL, IRMOVL } : 0; # Other instructions don't need ALU ]; ## Set the ALU function int alufun = [ icode in { OPL } : ifun; icode in { CMPL } : ALUSUB; 1 : ALUADD; # DECL and INCL are handled by it ]; ## Should the condition codes be updated? bool set_cc = icode in { OPL, CMPL, DECL, INCL }; ################ Memory Stage ################################### ## Set read control signal bool mem_read = icode in { MRMOVL, POPL, RET }; ## Set write control signal bool mem_write = icode in { RMMOVL, PUSHL, CALL }; ## Select memory address int mem_addr = [ icode in { RMMOVL, MRMOVL, PUSHL, CALL } : valE; icode in { POPL, RET } : valA; # Other instructions don't need address ]; ## Select memory input data int mem_data = [ icode in { RMMOVL, PUSHL } : valA; (icode == CALL) : valP; # Default: Don't write anything ]; ################ Program Counter Update ############################ ## What address should instruction be fetched at int new_pc = [ icode == CALL : valC; icode == JXX && Bch : valC; icode == RET : valM; 1 : valP; ];