Département IF / Architecture Matérielle TD+TP : Architecture des Ordinateurs 2016/2017 Partie I: Micromachine Dans cette série de TP nous allons construire un processeur pur 8-bit, avec les spécifications suivantes : — ses bus d’adresse et données sont sur 8 bits ; — le seul type de donnée supporté est l’entier 8 bits signé ; — il possède deux registres de travail de 8 bits, notés A et B. Au démarrage du processeur, tous les registres sont initialisés à 0. C’est vrai pour A et B, et aussi pour le PC : le processeur démarre donc avec le programme à l’adresse 0. Les instructions offertes par ce processeur sont Instructions de calcul à un ou deux opérandes par exemple B -> A not B -> A 21 -> B LSR A -> A A + B -> A A xor 12 -> A B xor -42 -> A B - A -> A ; Explications : — la destination (à droite de la flèche) peut être A ou B. — Pour les instructions à un opérande, celui ci peut être A, B, not A, not B, ou une constante signée de 8 bits. L’instruction peut être NOT (bit à bit), ou LSR (logical shift right). Remarque : le shift left se fait par A+A->A. — Pour les instructions à deux opérandes, le premier opérande peut être A ou B, le second opérande peut être A ou une constante signée de 8 bits. L’opération peut être +, -, and, or, xor. Instructions de lecture ou écriture mémoire parmi les 8 suivantes : *A -> A *A -> B A -> *A B -> *A *cst -> A *cst -> B A -> *cst B -> *cst La notation *X désigne le contenu de la case mémoire d’adresse X (comme en C). Comprenez bien la différence : A désigne le contenu du registre A, alors que *A désigne le contenu de la case mémoire dont l’adresse est contenue dans le registre A. Sauts absolus inconditionnels par exemple JA 42 qui met le PC à la valeur 42 Sauts relatifs conditionnels par exemple JR -12 qui enlève 12 au PC JR offset JR offset IFZ JR offset IFC JR offset IFN exécutée si Z=1 exécutée si C=1 exécutée si N=1 Cette instruction ajoute au PC un offset qui est une constante signée sur 5 bits (entre -16 et +15). Précisément, l’offset est relatif à l’adresse de l’instruction JR elle-même. Par exemple, JR 0 est une boucle infinie, et JR 1 est un NOP (no operation : on passe à l’instruction suivante sans avoir rien fait). La condition porte sur trois drapeaux (Z,C,N). Ces drapeaux sont mis à jour par les instructions arithmétiques et logiques. — Z vaut 1 si l’instruction a retourné un résultat nul, et zéro sinon. — C recoit la retenue sortant de la dernière addition/soustraction, ou le bit perdu lors d’un décalage. — N retient le bit de signe du résultat d’une opération arithmétique ou logique. Comparaison arithmétique par exemple B-A? ou A-42? Cette instruction est en fait identique à la soustraction, mais ne stocke pas son résultat : elle se contente de positionner les drapeaux. 1 1 Encodage du jeu d’instruction Les instructions sont toutes encodées en un octet comme indiqué par la table 1. Pour celles qui impliquent une constante (de 8 bits), cette constante occupe la case mémoire suivant celle de l’instruction. La table 2 décrit la signification des différentes notations utilisées. TABLE 1 – Encodage du mot d’instruction bit 7 instruction autres que JR saut relatif conditionnel 0 1 6 5 4 3 codeop, voir table 3 cond, voir table 4 2 1 arg2S arg1S offset signé sur 5 bits 0 destS TABLE 2 – Signification des différents raccourcis utilisés Notation encodé par dest arg1 arg2 offset destS=instr[0] arg1S=instr[1] arg2S=instr[2] instr[5 :0] valeurs possibles A si destS=0, B si destS=1 A si arg1S=0, B si arg1S=1 A si arg2S=0, constante 8-bit si arg2S=1 offset signé sur 5 bits TABLE 3 – Encodage des différentes opérations possibles codeop mnémonique remarques 0000 arg1 + arg2 -> dest addition ; shift left par A+A->A 0001 arg1 - arg2 -> dest soustraction ; 0 -> A par A-A->A 0010 arg1 and arg2 -> dest 0011 arg1 or arg2 -> dest 0100 arg1 xor arg2 -> dest 0101 LSR arg1 -> dest logical shift right ; bit sorti dans C ; arg2 inutilisé 0110 arg1 - arg2 ? comparaison arithmétique ; destS inutilisé 1000 (not) arg1 -> dest not si arg2S=1, sinon simple copie 1001 arg2 -> dest arg1 inutilisé 1101 *arg2 -> dest lecture mémoire ; arg1S inutilisé 1110 arg1 -> *arg2 écriture mémoire ; destS inutilisé 1111 JA cst saut absolu ; destS, arg1S et arg2S inutilisés Remarque : les codeop 0111, 1010, 1011, et 1100 sont inutilisés (réservés pour une extension future...). TABLE 4 – Encodage des conditions du saut relatif conditionnel cond mnémonique 00 (toujours) 01 10 11 IFZ IFC IFN si zéro si carry si négatif 2 2 Lisons un peu d’assembleur Que font les programmes suivants ? Trois petits programmes d’échauffement à gauche, un gros programme à droite. prog1: 21 42 B+A -> A -> B -> B prog2: *21 *42 B+A B -> A -> B -> B -> *43 prog3: *100 -> A *101 -> B B-A ? JR +2 IFN B -> A A -> *102 Validez avec un enseignant. Validez avec un enseignant. init: -128 -> A A -> *98 A-A -> A A -> *255 bcl: 100 -> A *255 -> B B+A -> A *A -> B *98 -> A B-A? JR +3 IFN B -> *98 *255 -> A 1 -> B B+A -> B B -> *255 *99 -> A B-A? JR +3 IF Z ; JA bcl fini: 3 ; ; ; ; ; 0->A en 1 octet index adresse du tableau T i ainsi B contient T[i] +3 : le JA est en 2 octets 3 Assemblons et désassemblons La section 1 précise le codage de chaque instruction sous forme binaire. 3.1 Assemblage Pour chacun de nos programmes d’échauffement, écrivez à droite de chaque instruction son code hexadécimal. Par exemple, on lit dans les tableaux de la section 1 que le code de 21->A est composé des deux octets : 01001100 (0x4C) suivi de la constante 21 (0x15). N’hésitez pas à commencer par l’écrire en binaire. Code assembleur prog1: 21 42 B+A -> A -> B -> B prog2: *21 *42 B+A B -> A -> B -> B -> *43 binaire hexadécimal prog3: *100 -> A *101 -> B B-A ? JR +2 IFN B -> A A -> *102 Validez avec un enseignant. (Ce travail stupide et dégradant s’appelle l’assemblage (assembly), et vous avez déjà envie d’écrire un programme qui le fait pour vous. Un tel programme s’appelle un assembleur.) 3.2 Désassemblage Quel est le programme qui a pour codes hexadécimaux 4C, 11, 4D, 47, 32, E2, 42, 80 ? Quel est le programme qui a pour codes hexadécimaux 11, 4D, 47, 32, E2, 42, 80 ? Concluez-en qu’il ne faut pas se tromper dans ses calculs d’offset quand on fait un saut. Validez avec un enseignant. (Ce travail rébarbatif et vexatoire s’appelle le désassemblage (disassembly), et le programme correspondant s’appelle un désassembleur – c’est le -d de objdump -d.) 4 4 Architecture du processeur La micro-architecture de ce processeur est donnée par la figure 1. Nous allons commencer par la passer en revue. Vous trouverez sur Moodle un squelette de ce processeur. Quelques remarques : — Comme d’hab, deux fils qui ont le même nom sur le dessin sont implicitement connectés. N’hésitez pas à ajouter des fils explicites sur le dessin si cela vous aide. Mais je conseille quand même le crayon à papier... — Les signaux dont le nom commence par “we” sont les write enable des registres correspondants. — Quand la taille d’un vecteur de bits n’est pas explicitée, c’est qu’il s’agit d’un vecteur de 8 bits. — L’automate est chargé de positionner tous les write enable (y compris Mwe pour la mémoire). Vous allez implémenter cet automate à la partie 6. Pour chaque question ci-dessous, on vous demande deux choses : 1/ repasser la partie correspondante du dessin, et 2/ indiquer la partie correspondante dans le circuit Logism. A la fin de cette partie, vous aurez normalement repassé toute la figure 1 avec de jolies couleurs, et compris le circuit Logisim... 4.1 Le PC et le cycle de von Neumann Prenez un crayon rouge. Ou est le registre PC ? Comment peut-il être envoyé sur le bus d’adresse de la mémoire ? Par quel chemin peut-on ajouter 1 au PC ? Où est le registre d’instruction ? Où est le registre de constante ? 4.2 Les branchements Prenez un crayon vert. Repassez d’abord ce qui permet de réaliser le JA. Puis ce qui permet de réaliser le JR. Les deux signaux ja et jr ne sont définis nulle part. Donnez en les équations logiques, à partir des bits du mot d’instruction. offset est un entier signé de 5 bits extrait du registre d’instruction. Comment peut-on l’étendre à un mot de 8 bits ayant la même valeur ? 4.3 Les instructions arithmétiques Prenez un crayon bleu. Repassez les registres A et B. Repassez l’ALU. Comment sont choisis l’operande 1 et l’opérande 2 ? Comment est choisie la destination ? 4.4 Les accès mémoire Prenez un crayon mauve ou turquoise ou jaune vif. Pour faire une lecture mémoire, il faut d’abord positionner l’adresse qu’on veut accéder sur le bus d’adresse. Montrez par quel chemin cela se fait. La donnée arrive ensuite sur le bus MDI. Montrez comment elle va pouvoir rejoindre les registres A ou B. Pour faire une écriture, c’est pareil mais c’est le contraire. N’oubliez pas de lever ceM. Validez avec un enseignant. 5 F IGURE 1 – Les différents blocs fonctionnels de notre processeur 6 5 Construction de l’ALU Si vous avez le sens de l’observation, vous avez compris que le circuit Logisim téléchargé sur Moodle n’est pas complet. Vous avez reçu la partie ennuyante et vous réaliserez la partie la plus intéressante : l’ALU et l’automate. Question 5-1. Pourquoi arg2S intervient-il deux fois dans le calcul des entrées (figure 1). Afin d’obtenir une ALU complète, vous allez dans un premier temps implémenter les fonctionnalités suivantes (ne vous occupez pas des drapeaux pour l’instant) : codeop 0000 0001 0010 0011 0100 0101 1000 1001 mnémonique arg1 + arg2 -> dest arg1 - arg2 -> dest arg1 and arg2 -> dest arg1 or arg2 -> dest arg1 xor arg2 -> dest LSR arg1 -> dest (not) arg1 -> dest arg2 -> dest remarques addition ; shift left par A+A->A soustraction ; 0 -> A par A-A->A logical shift right ; bit sorti dans C ; arg2 inutilisé not si arg2S=1, sinon simple copie arg1 inutilisé Une fois ces opérations disponibles dans votre ALU, vous allez vous charger de donner les bonnes valeurs à vos drapeaux. On rappelle que les drapeaux sont systématiquement mis à jour dans l’ALU, mais ne sont pas utilisés/enregistrés plus loin dans le circuit. Les drapeaux Z et N ont un sens pour chacune des instructions du tableau ci-dessus, en revanche C n’est valable que pour quelques opérations. Vous mettrez donc C à 0 lorsque l’opération en cours ne peut donner une valeur à C. Testez votre composant avant de passer à la suite. 6 Construction de l’automate Dans cette partie, vous allez implémenter un automate qui va piloter votre datapath. Vous allez donc commencer par implémenter un automate aux fonctionnalités réduites. Il vous servira de base pour la suite. 6.1 Les instructions arithmétiques en 1 octet Afin d’implémenter les instructions en 1 octet, nous vous donnons un automate représentant le cycle de Von Neumann (Figure 2). Pour rappel, le cycle de Von Neumann est le suivant : — Lire une instruction en mémoire à l’adresse PC, puis l’enregistrer dans le registre d’instruction (InstrFetch) — Selectionner l’instruction à exécuter (InstrDecode) (ici vous ne ferez rien pour l’instant car vous n’avez qu’un seul choix possible !) — Enregistrer le resultat de l’opération (RegWrite), et incrémenter PC. Question 6-1. Voyez dans le logisim tous les signaux qu’il doit lever, et essayez de les placer dans les patates au crayon à papier. Gardez en tête que cet automate ne permet que la réalisation d’opérations de l’ALU sans constantes. Il faudra donc ajouter plus tard, non seulement la gestion des constantes, mais aussi des accès mémoire. C’est relativement simple, sauf pour cePC : le plus simple est de le lever juste avant InstrFetch puisque c’est dans InstrFetch que le PC est utilisé. Question 6-2. Construisez un automate à patate chaude qui implémente ces trois états et positionne les signaux nécessaires. Les signaux que votre automate ne lève pas doivent être mis à 0. Question 6-3. Testez-les sur le programme suivant (que fait-il ?), que vous assemblerez et placerez au début de la mémoire : not b-a b-a b-a b-a b-a a -> a -> b -> b -> b -> b -> b 7 start InstrFetch XXXXXXXXX RegWrite XXXXXXXXX InstrDecode XXXXXXXXX F IGURE 2 – Le cycle de von Neumann sans accès mémoire, sans constantes et sans sauts Question 6-4. Si vous observez la machine de von Neumann, vous verrez que le bus d’adresse fait n’importe quoi entre deux instructions. Expliquez pourquoi ce n’est pas grave. 6.2 Le saut relatif (inconditionnel pour commencer) Comme il tient lui aussi en 1 octet, on peut le greffer à notre automate (Figure 3). Dans la même démarche que pour la question précédente, essayer dans un premier temps de trouver les signaux à lever dans l’état DoJR au crayon à papier avant de l’implémenter. Attention, votre état InstrDecode possède maintenant deux destinations ! Il vous faut donc mettre des conditions sur les flèches sortantes de cet état. Ne vous préoccupez pas des sauts conditionnels à ce stade. Testez en remplaçant par une boucle la série de b-a -> b dans le programme ci-dessus. start InstrFetch XXXXXXXXX RegWrite XXXXXXXXX DoJR XXXXXXXXX InstrDecode XXXXXXXXX F IGURE 3 – Le cycle de von Neumann sans constantes, sans accès mémoire avec sauts relatifs 6.3 Les instructions arithmétiques en 2 octets Il s’agit d’ajouter les états IncrPC et CstFetch (Figure 4). Précisez les signaux que doivent positionner les états IncrPC et CstFetch. N’oubliez pas que PC doit être incrémenté. 8 Testez sur prog1. start InstrFetch XXXXXXXXX RegWrite XXXXXXXXX DoJR XXXXXXXXX InstrDecode XXXXXXXXX IncrPC XXXXXXXXX CstFetch XXXXXXXXX F IGURE 4 – Le cycle de von Neumann avec constantes et sauts relatifs, mais sans accès mémoire ni sauts absolus 6.4 Le saut absolu Afin d’implémenter le saut absolu, vous devez ajouter un état à l’automate de la Figure 4. Faites valider par un enseignant avant d’implémenter votre solution. Vous pouvez tester votre implémentation en faisant : not a -> a b-a -> b Ja 1 6.5 Les sauts relatifs conditionnels Afin de pouvoir faire des sauts conditionnels, on peut regarder la valeur des drapeaux et ainsi faire un saut ou non. Pour l’instant, à chaque opération de l’ALU, votre processeur écrit le résultat dans un registre. On souhaite alors ajouter à l’alu une instruction de comparaison qui ne mettra pas à jour les registres mais seulement les drapeaux. On va donc ajouter l’instruction : codeop 0110 mnémonique arg1 - arg2? remarques Produit le résultat de arg1-arg2 sur la sortie mais ce ne sera pas enregistré dans un registre Il faut donc que cette instruction prenne un chemin différent dans l’automate. On propose alors l’automate de la Figure 5. Il faut créer un signal condTrue qui compare l’état des drapeaux avec les bits de condition. Ce signal participera à choisir si l’automate doit aller de InstrDecode vers DoJR ou NoJR. Testez votre implémentation sur le programme suivant : 42 -> b 7 -> a f: b-a? JR +3 IFN 9 start InstrFetch XXXXXXXXX RegWrite XXXXXXXXX DoJR XXXXXXXXX NoRegWrite XXXXXXXXX InstrDecode XXXXXXXXX IncrPC XXXXXXXXX NoJR CstFetch XXXXXXXXX XXXXXXXXX F IGURE 5 – Le cycle de von Neumann avec constantes et sauts relatifs conditionnels, mais sans accès mémoire ni sauts absolus b-a -> b JR -3 Quelle est la fonction f (a, b) calculée par ce programme ? 6.6 Les accès mémoire Afin de réaliser les accès mémoire, nous vous proposons l’automate de la Figure 6. Dans l’état MemWrite on se contente de positionner les bus adresse et donnée comme il faut. Dans l’état MemRead, on positionne l’adresse, puis on lève weDestReg comme dans l’état RegWrite. 10 start InstrFetch XXXXXXXXX RegWrite XXXXXXXXX DoJR XXXXXXXXX NoRegWrite XXXXXXXXX InstrDecode XXXXXXXXX IncrPC XXXXXXXXX NoJR CstFetch XXXXXXXXX XXXXXXXXX MemRead MemWrite XXXXXXXXX XXXXXXXXX F IGURE 6 – Le cycle de von Neumann ressemble plutôt à un plat de nouilles Partie II: 1 Micromachine - interruptions Spécification / cahier de charges Dans cette séance nous reviendrons sur le processeur « Micromachine « que nous avons traité au début de ce module (voir la Partie I). L’objectif est de réaliser une modification lui permettant de gérer les interruptions. Une interruption est un événement qui peut être provoqué soit par une cause externe (il y a des broches pour cela sur la puce du processeur), soit par une cause interne (division par zéro, accès mémoire interdit, accès d’un adresse mémoire délocalisée sur disque (mémoire virtuelle) etc.). Lorsque survient un tel événement, le processeur interrompt (temporairement) ce qu’il était en train de faire (son programme) pour sauter à la routine de traitement des interruptions (ISR = Interrupt Service Routine). Typiquement, c’est un mécanisme en deux parties : 1. le saut à l’adresse de la routine de traitement. 11 2. A la fin de la routine, le retour au programme interrompu. Ceci est déclenchée par une instruction dédiée. Le saut va devoir faire deux choses en une instruction (i) sauvegarder l’adresse de retour, c’est à dire l’adresse à laquelle il faudra reprendre l’exécution du programme une fois l’interruption traitée et (ii) charger l’adresse de la routine d’interruption dans le registre Program Counter. Il est important que ces deux actions soient faites en une seule instruction (atomique) pour ne pas permettre qu’une interruption intervienne entre les deux. Plus précisément, ce qui doit être atomique est la sauvegarde de l’adresse de retour : si on le fait en plus d’une instruction, une nouvelle interruption qui interviendrait entre ces instructions provoquerait un second déroutement ce qui deviendrait très compliqué à gérer. Dans ce TD, nous utiliserons un mécanisme simple pour garantir l’atomicité de l’opération call. Lors qu’une interruption est détectée par le processeur, l’instruction en cours est terminée. Puis, l’adresse de retour est enregistrée dans un nouveau registre dédié. Les drapeaux sont également enregistrés dans un registre dédié (pourquoi ?). Le PC est modifié pour accueillir l’adresse de la routine de traitement. Ensuite, on continue avec le cycle Fetch. 2 Conception de la solution sur papier Dans la suite de ce TD, on modifiera notre micromachine de façon à gérer des interruptions déclenchées par un signal externe supplémentaire nommé IRQ. Ce signal est équivalent à un niveau haut sur un fil reliant le matériel en question au processeur par une broche dédiée. Le processeur signalera le traitement de l’interruption pour que le matériel externe (à l’origine de l’interruption) puisse (i) s’assurer que la requête d’interruption a bien été traitée et (ii) arrêter d’émettre le signal IRQ. Pour ce faire, on ajoutera un signal IntAck (pour Interrupt Acknowledgement) sortant du chemin de données. Ce signal est controlé par l’automate. Il est à distination du composant matériel émettant les interruptions et est donc une sortie du processeur. Important : IRQ et IntAck sont des signaux interfaçant le CPU avec le périphérique en question. Physiquement, il s’agît donc de broches spécifiques sur la puce du processeur. 2.1 Modification du chemin de données Reprenez le magnifique dessin dans la figure 1. Prenez un style de votre couleur préférée, et : 1. Ajoutez deux registres nommés SavedPC et SavedFlags accueillant, respectivement, la sauvegarde du PC et la sauvegarde des drapeaux. 2. Modifiez le circuit pour rendre possible la sauvegarde du PC et des drapeaux. Notez les signaux supplémentaires à gérer par l’automate. 3. Modifiez le circuit pour rendre possible la reprise (restore) des valeurs sauvegardées (PC et drapeaux). Notez les signaux supplémentaires à gérer par l’automate. 4. Modifiez le circuit pour rendre possible le saut à l’adresse de la routine traitant les interruptions, ce qui revient à charger une valeur dans le PC. Ici, on supposera que cette adresse est fixe et égale à 128 == 0x80. Notez le signal supplémentaire à gérer par l’automate. Validez avec un enseignant. 2.2 Modification de l’automate Dans la figure 7 vous trouvez une copie de l’automate conçu dans de la partie I. 1. Ajoutez un (seul) état supplémentaire à l’automate, nommé JumpToISR, réalisant la partie call : (i) sauvegarde du PC et des drapeaux et (ii) saut à l’adresse de la routine traitant les interruptions. 2. La sauvegarde du PC et la mise à jour du PC peuvent être réalisées simultanément, c’est-à-dire dans le même état. Pourquoi ? 3. Ajoutez les transitions allant des autres états vers l’état JumpToISR tel que cet état est atteint à la fin du cycle de Von Neumann si le signal IRQ est actif. On peut donc supposer que ce signal reste stable jusqu’au traitement de l’interruption. 4. Ajoutez la transition pour retourner au cycle fetch après déclenchement du traitement. 5. Ajoutez le traitement du signal sortant IntAck signalant que l’interruption a été traitée. 12 start InstrFetch XXXXXXXXX RegWrite XXXXXXXXX DoJR XXXXXXXXX NoRegWrite XXXXXXXXX InstrDecode XXXXXXXXX IncrPC XXXXXXXXX NoJR CstFetch XXXXXXXXX XXXXXXXXX MemRead MemWrite XXXXXXXXX XXXXXXXXX F IGURE 7 – Le cycle de von Neumann (sans gestion des interruptions) Notre processeur est désormais capable de déclencher une routine traitant des interruptions ... mais pas de revenir. Pour cela, nous ajouterons une instruction nommée RETI. Elle sera encodée comme 86 = 0x58 = 0101100b. Le lecteur motivé peut vérifier dans la table 3 (partie I) que le code opératoire 1011 n’a pas encore été utilisé par une instruction existante. 1. Ajoutez un (seul) état supplémentaire à l’automate, nommé doRETI, réalisant le travail demandé, c’est-àdire la restauration des sauvegardes du PC et des drapeaux. 2. Ajoutez les transitions vers cet état et sortant de cet état. Validez avec un enseignant. 13 3 Réalisation sur logisim Sur moodle vous trouvez une solution de la micromachine sans gestion des interruptions. Cette solution comprend également un bouton poussoir permettant à l’utilisateur de demander le déclenchement d’une interruption. Pour le moment, ce signal n’est pas traité par le processeur. 3.1 Modification du chemin de données 1. Ajoutez les deux registres SavedPC et SavedFlags accueillant, respectivement, la sauvegarde du PC et la sauvegarde des drapeaux. 2. Connectez les registres pour permettre (i) la sauvegarde du PC et des drapeaux, (ii) la reprise des valeurs sauvegardées (PC et drapeaux). Pre-voyez des signaux pour déclencher ces 4 actions. 3. Ajoutez une possibilité d’enregistrer la valeur 0x80 dans le PC (saut vers la routine de traitement / ISR). Pre-voyez un signal pour déclencher cette action. 3.2 Modification de l’automate 1. Ajoutez l’état JumpToISR et les transitions le concernant. 2. Ajoutez l’état doRETI et les transitions le concernant. Pour cela il faut une comparaison du code opératoire de l’instruction (issue du registre IR) avec la constante 1011. 3. Ajoutez les signaux sortants pour ces deux états : sauvegarde PC et drapeaux ; reprise PC et drapeaux ; saut vers l’ISR ; Testez votre circuit avec un programme. Sur moodle vous trouvez un code en deux partie : programme principale démarrant à l’adresse 0 + ISR démarrant à l’adresse 0x80. 14 Partie III: MSP 430 - Prise en main Dans cette série de TP «msp430», on va étudier le fonctionnement d’un (petit) ordinateur réel, pour mieux comprendre l’interface entre le logiciel et le matériel. Vous devrez donc faire les diverses manipulations demandées, et par moment écrire des bouts de programme. Nous ne ramasserons pas de compte-rendu ; par contre, vous avez intérêt à prendre des notes tout au long du déroulement du TP pour pouvoir les relire par la suite : dans les TP d’après, mais aussi avant les QCM, et aussi avant l’examen ! Pour chaque exercice, mettez donc par écrit (sur papier ou sur ordinateur) les manips que vous faites, les questions que vous vous posez, et les nouvelles notions que vous comprenez. Comme tout objet technologique, notre plate-forme de TP s’accompagne d’une documentation technique abondante. Pour ne pas vous noyer sous la doc, nous vous en avons copié les extraits essentiels directement dans le sujet, sous forme d’encadrés. Pour les plus curieux, nous vous avons aussi mis à disposition les documents sur Moodle : motherboard.pdf décrit notre carte d’expérimentation et les différents composants présents sur la carte. msp430x4xx.pdf est le manuel générique de la famille MSP430. Le processeur est documenté au chapitre 3 de ce document. datasheet-msp430g4618.pdf donne les détails techniques de notre modèle précis de msp430. 1 Découverte de la carte Pour chaque binôme, allez prendre le matériel nécessaire au TP : une carte d’expérimentation, une sonde JTAG (le boîtier gris avec une nappe d’un côté), et un câble USB. Exercice 1 Que signifie l’acronyme USB, au fait ? Expliquez en une phrase la signification du S. Faites valider cette phrase par un enseignant, mais n’attendez pas qu’il arrive pour passer à la suite. 2 Que signifie l’acronyme JTAG ? L’explication détaillée est donnée dans l’encadré page 21, que nous 4.Exercice Functional Overview lirons en temps utile. The MSP430FG4618/F2013 experimenter’s board supports various applications through the use :ofmotherboard.pdf the on-chip peripherals connecting to a number of on-board Extrait de la documentation page 6 components and interfaces as shown in Figure 2. Wireless CC1100/ 2420/2500 EMK Interface Analog Out LCD RS-232 Buzzer JTAG1 FG4618 JTAG2 Microphone F2013 Capacitive Touch Pad Buttons Figure 2: Experimenter’s board block diagram Wireless communication is possible through the expansion header which is 15 compatible with all Chipcon Wireless Evaluation Modules from Texas Instruments. Interface to a 4-mux LCD, UART connection, microphone, audio output jack, Sur cette carte mère, en plus du msp430, il y a tout un tas de périphériques : 1. 2. 3. 4. 5. 6. 7. 8. 9. etc. un écran à cristaux liquides (pour afficher des chiffres et des icônes) un microphone un buzzer (pour jouer du son) une prise casque (pour jouer du son aussi, mais plus joli) un quartz (pour générer le signal d’horloge) deux boutons poussoirs des voyants lumineux (LED) une roue tactile capacitive (touchpad) en forme de chiffre 4 un port série (RS-232) ... Exercice 3 Pour chacun de ces éléments, indiquez sur le schéma page précédente son emplacement approximatif. Certains éléments (LEDs, quartz) ne sont pas sur le schéma, vous devrez les chercher directement sur la carte. Le quartz est repéré X2, et les diodes sont repérées LED1, LED2, LED3 et LED4. Commentaire La carte comporte deux microcontrôleurs. L’un est un MSP430F2013 (c’est le petit), et l’autre un MSP430FG4618 (c’est le gros). C’est avec ce second msp430 qu’on va travailler dans ces TP. Les diodes LED1, LED2, et LED4 y sont connectées par des pistes de la carte mère. La diode LED3, par contre, est connectée au F2013, qu’on ne va pas utiliser du tout. Vous pouvez dès maintenant oublier son existence, ainsi que celle de la LED3. Exercice 4 L’encadré page suivante montre le schéma électrique de la carte mère. Retrouvez les différents composants vus jusqu’ici, et indiquez leur emplacement sur le schéma. 16 Extrait de la documentation : motherboard.pdf page 19 1 3 5 7 1 3 5 7 H2 H3 H4 H5 H6 H7 UCA0SIMO UCA0CLK P7.5 P7.7 P5.7 P5.5 2 4 6 8 UCB0SDA UCB0CLK P3.5 P3.7 2 4 2 4 6 8 11 P6.0 P6.2 P6.4 P6.6 VEREFP5.0 P10.6 1 3 5 7 1 3 5 H8 H9 2 4 6 8 2 4 6 P6.1 P6.3 P6.5 P6.7 VEREF+ P5.1 P10.7 VCC 2 1 LCL_PWR1 3 2 FET_PWR1 1 LCL_PWR2 3 2 FET_PWR2 1 VCC_1 VCC_2 14 12 10 8 6 4 2 13 11 9 7 5 3 1 GND GND VREG_EN RESETCC JTAG1 AVCC_4618 C8 LCL_PWR1 FET_PWR1 C7 10uF 0.1uF 1 3 5 7 9 11 13 15 17 19 FIFO FIFOP GDO0 GDO2 P4.2 UCLK1 SIMO1 SOMI1 VCC C1 0.1uF GND 1 3 5 7 9 11 13 15 17 19 RF Daughter Card Connect 2 4 6 8 10 12 14 16 18 20 DVCC_4618 RF1 C2 0.1uF RF2 2 4 6 8 10 12 14 16 18 20 R5 8 7 6 DVCC_4618 2 5 8 7 6 SW1 GND SW2 R21 0-DNP 0-DNP R22 1A_1B_1C_1D 1F_1G_1E_DP1 2A_2B_2C_2D 2F_2G_2E_DP2 3A_3B_3C_3D 3F_3G_3E_COL3 4A_4B_4C_4D 4F_4G_4E_DP4 5A_5B_5C_5D 5F_5G_5E_COL5 6A_6B_6C_6D 6F_6G_6E_DP6 7A_7B_7C_7D 7F_7G_7E_DP7 C3 10uF P3.5 1 JP1 2 SBWTDIO SBWTCK DOL_ERR_MINUS_MEM ENV_TX_RX_8BC ANT_A2_A1_A0 BT_B1_B0_BB AU_AR_AD_AL PL_P0_P1_P2 F1_F2_F3_F4 F5_PR_P4_P3 COM0 COM1 COM2 COM3 SoftBaugh SBLCDA4 (For opt. F2013 programming) DVCC_4618 P$14 P$13 P$12 P$11 P$10 P$9 P$8 P$7 P$6 P$5 P$4 P$3 P$2 P$1 1N4148 D2 GND 2 5 DVCC_4618 PS8802 3 U2 PS8802 3 U1 Isolated RS232 Communication 100 Q1 MMBT5088 VCC R9 2k2 GND UCA0TXD UCA0RXD P2.6 P2.7 P3.0 UCB0SDA UCB0SCL UCB0CLK P3.4 P3.5 P3.6 P3.7 UTXD1 C13 URXD1 GND 0.1uF DVCC_4618 LCDCAP P5.7 C15 P5.6 P5.5 10uF COM3 COM2 COM1 GND COM0 P4.2 UCA0RXD UCA0TXD GND GND 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 1k MSP-EXP430FG4618 PCB Ver 0-00 Document Number: 5 4 3 2 1 RS232 10uF C4 G1 G2 9 8 7 6 VER: 0-00 S21 S20 S19 S18 S17 S16 S15 S14 COM0 COM1 COM2 COM3 PC_GND Buzzer Mute P$26 P$25 P$24 P$23 P$22 P$21 P$20 P$19 P$18 P$17 P$16 P$15 Sheet: 1/1 MSP430FG4618/F2013 Experimenter's Board S0 S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 R2 Power Supply Configuration BATT GND C6 VCC_1: FG4618 Supply Config VCC_2: F2013 Supply Config C5 DVCC_4618 AVCC_4618 Pos 1-2: FET Powered Pos 2-3: Battery Powered VCC R8 10 R10 10 10uF 0.1uF GND Audio output jack R1 MSP430FG4618 Pin Access 1 3 1 3 5 7 LCDCAP P5.6 P7.0 UCA0SOMI P7.4 P7.6 SW2 GDO2 VREG_EN FIFOP URXD1 SIMO1 UCLK1 P4.7 2 4 6 8 2 4 6 8 1 3 5 7 11 12 13 2 1 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 P6.5 (A5/OA2O) A1 R17 SW1 GDO0 RESETCC FIFO UTXD1 P4.2 SOMI1 P4.6 P3.0 UCB0SCL P3.4 P3.6 10 12 13 2 1 3 VREF C9 VEREF+ VEREFP5.1 P5.0 P10.7 P10.6 S0 S1 S2 S3 S4 S5 S6 S7 S8 S9 DVCC_4618 P6.3 P6.4 C10 P6.5 P6.6 10uF P6.7 VREF X2 GND 0.1uF GND JP3 1 2 GND P6.4 (A4/OA1I0) 2 JP4 1 R7 P2.1 P2.3 UCA0RXD P2.7 10 9 14 15 16 UCB0SDA UCB0SCL P3.0 UCB0CLK Sallen-Key 2nd Order OA1 Active LPF R25 15.4k (Output Attn.) + R4 R12 D3 1N4148 + SP1 9 8 14 15 16 2 4 6 8 P6.0 (A0/OA0I0) R24 1.4k C17 3.3nF R6 470 2 4 6 8 8 470 R20 150k P6.7 (A7/DAC1) C16 22nF LED2 D1 1N4148 Date: 26-Oct-2006 17 2AL60P1 470 470 + B1 - 2k2 2k2 S2 INNER_GND 2 FET_PWR2 4 LCL_PWR2 6 8 10 12 14 LED3 1k R29 R30 P6.1 (A1/OA0O) R11 INNER_GND JTAG2 GND SBWTDIO 1 3 5 SBWTCK 7 9 11 13 VSS VCC U4 GND 1 14 2 2013_P1.0 3 2013_P1.1 4 2013_P1.2 5 2013_P1.3 6 2013_P1.4 7 2013_P1.5 8 SCL H1 9 SDA 1 3 5 7 R16 Mic Input Circuitry and 1st Order OA0 Active HPF P2.3 (Mic Supply) P1.0/TACLK/ACLK/A0+ P1.1/TA0/A0-/A4+ P1.2/TA1/A1+/A4P1.3/VREF/A1P1.4/SMCLK/A2+/TCK P1.5/TA0/A2-/SCLK/TMS P1.6/TA1/A3+/SDO/SCL/TDI/TCLK P1.7/A3-/SDI/SDA/TDO/TDI C19 10uF C14 P6.2 (A2/OA0I1) PWR1 GND 1 3 5 7 C12 0.1uF NMI/RST/SBWTDIO TEST/SBWTCK XIN/P2.6/TA1 XOUT/P2.7 17 15 13 11 9 7 5 3 1 MSP430F2013PW 18 16 14 12 10 8 6 4 2 2 1 DNP P2.0 P2.2 UCA0TXD P2.6 VCC 2 PWR2 1 C11 GND 10 11 13 12 17 15 13 11 9 7 5 3 1 0 S1 100k 2 1 100k 2 1 5 5 LED1 BAND P$4 RING P$2 TIP P$1 C20 5M1 R32 22k 1uF 18 16 14 12 10 8 6 4 2 BB1 R31 Breadboard 17 15 13 11 9 7 5 3 1 BB2 GND 470n + P6.3 (A3/OA1O) R33 GND R28 47k 2013_P2.6 2013_P2.7 BB3 GND GND 470k 470 7 7 R3 10k R23 6 6 GND 47k 100 99 98 97 P6.2 96 P6.1 95 P6.0 94 93 92 91 90 89 88 87 SW1 86 SW2 X1 85 GDO0 84 GDO2 83 RESETCC 82 VREG_EN 81 FIFO 80 FIFOP 79 P2.0 78 P2.1 77 P2.2 76 P2.3 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 S10 S11 S12 S13 S14 S15 S16 S17 S18 S19 S20 S21 P7.7 P7.6 P7.5 P7.4 UCA0CLK UCA0SOMI UCA0SIMO P7.0 P4.7 P4.6 UCLK1 SOMI1 SIMO1 R14 470n LED4 5M1 C18 5M1 15p R13 470 R15 C21 VCC 18 16 14 12 10 8 6 4 2 R19 4 4 + R18 10 5M1 1 JP2 2 R26 M1 1k 3k3 R27 1 2 R34 VCC_2013 1.1 Vous avez dit microcontrôleur ? Le MSP430FG4618 est un microcontrôleur, c’est à dire un System-on-Chip : une même puce qui contient à la fois un processeur, de la mémoire, et des contrôleurs de+ périphériques. Si on zoome sur l’intérieur de la puce, on a donc affaire à l’architecture illustrée ci-dessous. Extrait de la documentation : datasheet-msp430g4618.pdf page 5 Les flèches repérées MAB et MDB sont respectivement le Memory Address Bus et le Memory Data Bus (les mêmes que dans la micro-machine). Ce sont eux qui relient le processeur au reste-du-monde, comme dans toute machine de von Neumann qui se respecte. Vous pouvez constater qu’ici, le reste du monde ne se limite pas à la mémoire comme dans notre micromachine... Nous allons détailler tout cela. Exercice 5 Repérez sur ce diagramme le processeur, la RAM, la mémoire flash. Vérifiez que vous connaissez le sens des acronymes RISC, CPU, RAM, ADC, DAC. Sinon, ouvrez le glossaire qui est tout au début de msp430x4xx.pdf (p. 4). Demandez des explications à un enseignant si nécessaire. Ignorez les autres acronymes pour le moment. Exercice 6 Branchez maintenant la sonde JTAG sur la carte. Vous devriez pouvoir choisir sans difficulté entre les deux connecteurs JTAG en regardant la figure de la page 15. 1.2 Zoom sur le processeur Si on se rapproche encore, on tombe sur l’architecture suivante : 18 Extrait de la documentation : msp430x4xx.pdf page 44 MDB MAB 15 0 R0/PC Program Counter 0 R1/SP Stack Pointer 0 R2/SR/CG1 Status R3/CG2 Constant Generator R4 General Purpose R5 General Purpose R6 General Purpose R7 General Purpose R8 General Purpose R9 General Purpose R10 General Purpose R11 General Purpose R12 General Purpose R13 General Purpose R14 General Purpose R15 General Purpose 16 16 Zero, Z Carry, C Overflow, V Negative, N dst src 16−bit ALU Commentaire Attention, ce schéma ne montre que la vue ISA (instruction-set architecture), c’est à dire du point de vue de l’utilisateur du processeur. Elle cache les détails de microarchitecture que le programmeur n’a pas besoin de connaître : l’automate de contrôle, le registre d’instruction, etc. Les seuls éléments représentés sur le schéma sont donc ceux qui sont accessibles au programmeur : les 16 registres architecturaux, les drapeaux, ainsi que l’unité arithmétique et logique. Remarquez au passage que les 4 premiers registres sont spécialisés pour un usage particulier (R0 est le compteur ordinal, etc). À l’inverse les 12 autres registres sont généraux, on peut y mettre ce qu’on veut. Nous vous avons reproduit en annexe (page 42 et suivantes) les passages correspondants de la documentation. Vous n’en avez pas besoin pour l’instant, mais pensez à y revenir lorsque vous voudrez des détails sur l’usage des registres. Exercice 7 contrôle. Sur le schéma de la page 18, indiquez où se trouvent nos 16 registres, ainsi que l’automate de Exercice 8 Explicitez l’acronyme ALU. Exercice 9 Tiens, il manque les flèches sur les fils entre ALU et les drapeaux. Ajoutez-les. 19 Exercice 10 Allez lire la page https://fr.wikipedia.org/wiki/Registre_de_processeur et résumez, en une phrase, la différence entre un registre spécialisé et un registre général. 2 Prise en main des outils : mspdebug Pour communiquer avec notre MSP430 au travers de l’interface USB/JTAG, on va utiliser un programme appelé mspdebug. Cet outil va nous permettre de charger des programmes dans la mémoire, d’observer et de contrôler l’exécution du programme, d’inspecter le contenu du CPU et de la mémoire, etc. Exercice 11 Branchez la carte, et lancez mspdebug en tapant la ligne commande suivante : mspdebug -j -d /dev/ttyUSB0 uif L’argument uif est le nom du driver à utiliser, ici celui de notre boîtier JTAG. Vous devez obtenir une série d’informations techniques compliquées, puis une liste des commandes disponibles, et enfin un prompt de la forme (mspdebug) en début de ligne. Commencez par effacer complètement la puce en tapant dans mspdebug la commande erase . On va maintenant se servir de mspdebug pour allumer et éteindre la diode LED4. Comme le montre la figure p. 18, tous les périphériques sont mappés sur des adresses mémoire. En écrivant les bonnes valeurs aux bonnes adresses, on peut contrôler ces périphériques. Par exemple, pour allumer cette diode, il faut d’abord écrire la valeur 2 à l’adresse 50. Cela fait, on allumera la diode en écrivant la valeur 2 à l’adresse 49, et on l’éteindra en écrivant 0 à l’adresse 49. Admettons ces valeurs pour l’instant, nous les expliquerons dans un moment. Exercice 12 Toujours dans mspdebug, tapez help mw et lisez l’aide de la commande memory write. Remar- quez au passage que vous pouvez aussi taper help tout court pour obtenir la liste des commandes disponibles, et help bidule pour obtenir de l’aide sur la commande bidule. Exercice 13 Faites s’allumer et s’éteindre la diode quelques fois. 20 À savoir : le JTAG L’acronyme JTAG désigne une méthode permettant de lire ou d’écrire n’importe quel bit de mémoire d’un circuit séquentiel. Cette méthode nécessite “seulement” des modifications mineures à l’intérieur du circuit, ainsi qu’une poignée de signaux connectés au monde extérieur (de deux à cinq fils, suivant les variantes du protocole). Le principe de base est simple : il s’agit de considérer virtuellement l’ensemble du circuit (ici le MSP 430) comme un seul gros automate, selon la figure suivante que vous connaissez maintenant bien. / s T x s0 / 1000 / reset Registre d’état 1000 s / F / y Ck Dans cette figure, le registre d’état est un énorme registre (1000 bits sur notre exemple) qui contient le registre de l’automate de contrôle, mais aussi tous les registres de la partie «datapath» : les registres de la boîte à registres, tous les registres de pipeline, la valeur des flags etc. Tout l’état du processeur, quoi. Si vous n’avez pas compris ce paragraphe, faites-le vous expliquer par un enseignant. On ajoute (de manière automatique) à chaque flip-flop de cet immense registre un tout petit peu de circuiterie pour faire de l’ensemble des 1000 registres binaires un unique immense registre à décalage. C’est une transformation automatique qui est décrite par la figure ci-dessous : Le registre d’état de la figure ci-dessus, avant... ... et après sa transformation en JTAG 1 R 0 R test 1 R 0 R test 1 R 0 21 TestDataIn test R TestDataOut Le JTAG, suite Avec tout cela, le circuit fonctionne normallement lorsque test est à 0. Et on peut, en 1000 cycles, mettre le circuit dans un état quelconque. Il suffit de mettre test à 1, et de pousser l’état qu’on veut dans le grand registre à décalage ainsi obtenu. Dans le même temps, l’état précédent du circuit sort sur testOut : on peut également, toujours en 1000 cycles d’horloge, lire l’état complet du processeur. C’est ce qu’on va faire dans ce TP pour contrôler l’exécution de notre programme en pas-à-pas, mais aussi pour observer quand on le désire les valeurs des registres. Mais pourquoi cela s’appele JTAG ? Parce que cela sert surtout à tester chaque puce, y compris les plus complexes comme votre Pentium, avant de le mettre en boîte. En effet, lors du processus de fabrication, il arrive souvent qu’une poussière malencontreuse rende un transistor inopérant. Comment détecter cette situation pour jeter les puces défectueuses au plus tôt ? Bien sûr, on pourrait lui faire booter Linux puis Windows et jouer un peu à Quake dessus, et on se dirait qu’on a tout testé. Mais cela prendrait de longues minutes par puce, et le temps c’est de l’argent. Voici une technique qui permet de tester toute la puce en quelque centaines de milliers de cycles seulement (comptez combien de cycles à 4GHz il faut pour booter Linux en 20s). — On met test à 1, puis on pousse un état connu, pas forcément utile, dans le processeur. — Puis on met test à 0, et on fait tourner le processeur pendant quelques centaines de cycles. — Il fait sans doute n’importe quoi, mais ce n’est pas grave. — On remet test à 1, et on sort l’état complet du processeur (tout en poussant un nouvel état). — On compare l’état obtenu avec l’état (obtenu par simulation) dans lequel doit être le processeur si chacune de ses portes fonctionne correctement. S’il y a une différence, on le jette ! — Et on recommence plusieurs fois, avec des états construits pour faire fonctionner tous les transistors de l’énorme fonction T – pas forcément des états dans lequel le processeur peut se trouver en fonctionnement normal. Tout ceci est même normalisé par le Joint Test Action Group : JTAG. Et le rapport avec notre interface JTAG ? Eh bien, une fois qu’on a ce mécanisme en place, on peut même s’en servir pour débugger : on peut aller observer ou changer la valeur de n’importe quel registre du processeur en quelque dizaines de milliers de cycle. Il suffit de lire l’état, changer les bits qu’on veut, et réécrire l’état modifié. C’est comme cela que vous pourrez, dans ce TP, observer dans mspdebug ce qui se passe à l’intérieur de votre MSP430. Il y a même une boîte nommée JTAG interface qui permet, à travers le JTAG, d’observer aussi tout le contenu de la RAM et des périphériques. Bien sûr, le nombre des registres et l’ordre dans lequel ils sont chaînés dépend du microcontrôleur utilisé, c’est pourquoi on doit passer les bons argumentsà mspdebug. 3 Assemblage et exécution d’un programme Exercice 14 vant : Créez un nouveau répertoire TPMSP430, et retapez 1 dans un fichier ex14.s le programme sui- .section .init9 main: /* initialisation de la diode rouge */ mov.b #2, &50 /* eteindre */ mov.b #0, &49 /* allumer */ mov.b #2, &49 1. Vous pouvez aussi essayer de copier-coller depuis le PDF, mais il faudra pas venir vous plaindre que ça marche pas (ce qui sera le cas). Et puis c’est réellement formateur de retaper les exemples (si, si). 22 loop: jmp loop Dans ce programme, — .section .init9 est une commande à destination de msp430-gcc pour lui indiquer où placer ce code – voir l’encadré ci-dessous. — mov.b est l’instruction assembleur qui réalise une copie (move) d’un octet (b pour byte). — en assembleur msp430, #17 désigne la valeur 17, alors que &17 désigne le contenu de la case mémoire d’adresse 17. — donc mov.b #2, &49 est une instruction assembleur qui réalise une copie de la valeur constante 2 vers le contenu de la case mémoire d’adresse 49. Attention, les arguments sont dans l’ordre inverse de la commande mw de mspdebug... Moyen mnémotechnique : en assembleur MSP430, la virgule se lit “to”. — jmp est une instruction MSP430 de saut (pour jump) — main: et loop: sont des définitions d’étiquettes (label). Une étiquette indique une adresse au programme assembleur. On peut les utiliser ensuite en place de la vraie adresse comme destination de sauts (ou autres). En cas de saut relatif, le programme assembleur calculera le déplacement par soustraction de l’adresse du saut à l’adresse de l’étiquette. — Ici, remarquez qu’on finit notre programme par une boucle infinie dont il ne sortira pas : cela assure que notre pointeur de programme ne part pas se balader au hasard dans la mémoire... Exercice 15 Traduisez ce programme en un exécutable en langage machine avec la commande suivante : msp430-gcc -mmcu=msp430fg4618 -mdisable-watchdog -o prog.elf ex14.s Les deux options sont importantes. La première, -mmcu=msp430fg4618, indique la puce exacte ciblée. La seconde, -mdisable-watchdog, débranche le watchdog qui fait rebooter le système lorsqu’il est inactif trop longtemps. Allez lire le premier paragraphe de la page wikipedia “watchdog timer” et vous comprendrez par quel mécanisme votre téléphone reboote lorsqu’il ralentit trop. Attention, si on fait une faute de frappe dans cette option, il n’y aura pas de message d’erreur mais le programme fera n’importe quoi, puisqu’il rebootera sans fin. À savoir : assemblage et éditions de liens Pour passer d’un programme en langage assembleur à un programme exécutable, il faut réaliser deux opérations : 1) l’assemblage consiste à convertir un fichier texte contenant des instructions vers un fichier binaire contenant les même instructions, mais en langage machine. L’outil qui fait ça, l’assembleur, est typiquement nommé as (et dans notre cas msp430-as), et permet de passer d’un fichier bidule.s à un fichier bidule.o. Mais ce n’est pas fini : le programme consiste peut-être en plusieurs morceaux, qu’il faut maintenant coller ensemble. 2) l’édition de liens consiste à coller ensemble plusieurs fichiers machin.o, et à placer chacun d’entre eux aux bonnes adresses, par exemple pour s’assurer qu’ils ne se marchent pas les uns sur les autres. L’outil qui fait ça, l’éditeur de liens, est typiquement nommé ld , et produit un fichier truc.elf Invoquer ces différents outils comme il faut avec les bonnes options est compliqué et souvent source d’erreur. Heureusement, il existe aussi une commande générique gcc qui est beaucoup plus simple d’usage, et qui se charge d’appeler as et ld dans le bon ordre et avec les bons arguments. Ainsi, vous pouvez obtenir directement un exécutable avec la commande donnée. Exercice 16 Désassemblez le programme obtenu par msp430-objdump -d prog.elf Cherchez, dans la sortie de cette commande, votre main, et répondez aux questions suivantes : — Quel est le code binaire de l’instruction jmp loop ? — A quelle adresse est-elle assemblée ? 23 — Est-ce un saut relatif ou un saut absolu ? — Qui s’est permis de rajouter toutes ces instructions autour de votre programme ? Depuis mspdebug, transférez votre programme sur la carte en utilisant la commande prog prog.elf , puis lancez-le avec la commande run . Constatez que la diode reste toujours allumée (c’est normal, on ne l’éteint jamais). Interrompez l’exécution en appuyant sur Ctrl+C. Exercice 17 4 Exécution d’un programme pas à pas Exercice 18 Copiez ex14.s en un nouveau fichier ex18.s, déplacez les instructions d’allumage et d’extinction à l’intérieur de la boucle infinie : le but est de faire clignoter la diode. Assemblez par msp430-gcc, chargez par load puis exécutez de nouveau votre programme par run dans mspdebug. Si tout va bien, on dirait que la diode reste encore toujours allumée. C’est peut-être que vous vous êtes trompés. C’est peut-être aussi qu’elle clignote bien, mais trop rapidement pour notre œil. En effet, la fréquence du CPU est de 1MHz, et chaque instruction prend une poignée de cycles d’horloge, donc notre boucle tout entière tourne à plus de 100kHz. Interrompez de nouveau l’exécution, et au lieu de la relancer avec run , utilisez cette fois la commande step qui exécute une seule instruction machine (faites donc help run et help step au passage). Constatez qu’en exécutant ainsi le programme en mode pas-à-pas, on arrive maintenant à voir ce qui se passe. Décidez ainsi si la diode clignote ou si vous vous êtes plantés. Auquel cas, corrigez. 5 Programmation en assembleur : variables et boucles Vous allez maintenant devoir modifier votre programme un peu plus sérieusement. Pour la syntaxe de l’assembleur MSP430, aidez-vous des explications qui sont données dans les deux encadrés page 26 et page 27. Débuggage : points d’arrêts Pour la mise au point, utilisez mspdebug. En plus des commande qu’on a vues jusqu’ici, vous aurez peutêtre besoin de la commande md (memory display) pour lire la mémoire, et de setbreak pour mettre des points d’arrêt. Pour plus de détails, help md et help setbreak. Exercice 19 Introduisons d’abord les registres et les opérations logiques. Modifiez le programme comme suit : .section .init9 main: mov.b #2, &50 mov #2, r15 loop: mov.b r15, &49 xor #2, r15 jmp loop /* initialisation de la diode */ /* valeur initiale de la valeur de la diode */ /* transferer r15 vers la diode */ /* que fait cette ligne? */ La nouveauté est l’utilisation l’un des registres introduits dans le dessin de la page 19. L’instruction xor #2, r15 met dans r15 le ou-exclusif (xor), bit à bit, de r15 et de la valeur 2. Remarquez que nous travaillons sur r15 avec des instruction sans le suffixe .b : ces instructions travaillent sur 16 bits, pas juste 8. Essayez de prédire ce que fait ce programme. Executez ce programme pas-à-pas, et observez dans la fenêtre mspdebug la valeur du registre r15 au cours de l’exécution. Exercice 20 Ajoutez au programme précédent ce qu’il faut pour que, tout en faisant clignoter la diode, il compte dans le registre r14 (il ajoute à chaque tour de boucle 1 au registre r14). Vérifiez que r14 augmente bien dans mspdebug. 24 Exercice 21 Modifiez votre programme afin de ralentir suffisamment la boucle infinie pour pouvoir observer le clignotement à l’oeil nu. Pour cela, vous allez rajouter, à l’intérieur de la boucle existante, une seconde boucle qui ne fait rien sauf perdre du temps. Ce sera l’équivalent d’une boucle for : elle incrémente un registre jusqu’à atteindre une certaine valeur, par exemple 20000. Attention, 20000 tient sur 16 bits mais pas sur 8 bits : utilisez des instructions sans l’extension .b. Pour sortir de cette boucle, vous pourrez utiliser une instruction de comparaison CMP et un saut conditionnel, par exemple JNE. C’est une question difficile (la première fois, après cela va tout seul) : si la page 26 ne vous suffit pas, ne restez pas bloqué, faites appel à un enseignant. 25 Survol de la syntaxe assembleur du msp430 On vous présente ici la syntaxe que vous allez devoir utiliser en TP. Elle est en général insensible à la casse (majuscules ou minuscules, c’est pareil). Opérations La plupart des instructions est de la forme OPCODE SRC, DST . OPCODE est l’opération souhaitée, par exemple ADD, XOR, MOV, etc. La liste complète est donnée page suivante. SRC et DST indiquent les opérandes (source et destination) sur lesquels travailler. La destination est aussi le second opérande de l’opération, ainsi la virgule peut souvent se lire “to”. Par exemple ADD #1, R5 peut se lire “ADD 1 to R5” et, en C++, s’écrirait R5=R5+1;. Une instruction spéciale est l’instruction MOV, par exemple MOV R7, R5 , qui peut se lire “MOV R7 to R5” et s’écrirait en C++ R5=R7; a En détail, chaque opérande est de l’une des formes suivantes : — un nom de registre : R7, R15... (utilisez les numéros, pas de «SP» ni «PC» etc.) — une constante immédiate, à préfixer par # : #42, #0xB600... — le contenu d’une case mémoire désignée par son addresse, à préfixer b par & : &1234, &0x3100... — le contenu d’une case mémoire dont l’adresse est la valeur contenue dans un registre, alors ce registre est préfixé par @. Par exemple MOV R7, @R5 , s’écrirait en C++ ainsi : *R5=R7; Par exemple, l’instruction ADD &1000, R5 calcule la somme de R5 et de la valeur contenue dans la case d’adresse 1000, et range le résultat dans R5. Attention, certaines combinaisons n’ont pas de sens, et seront rejetées par l’assembleur avec un message d’erreur. Par exemple l’instruction MOV R8, #36 ne veut rien dire. Certaines instructions travaillent sur un seul opérande, et ont donc une syntaxe légèrement différente. Par exemple INV DST inverse chacun des bits de DST, ou CLR DST met DST à zéro. Reportez-vous à la liste page ci-contre pour plus de détails, et/ou à la doc : msp430x4xx.pdf pages 56 et suivantes. Drapeaux Certaines instructions, notamment les opérations arithmétiques et logiques, modifient le registre d’état (R2, cf encadré page 44), en particulier les drapeaux Z, N, C, V : — Z est le Zero bit. Il passe à 1 lorsque le résultat d’une opération est nul, et il passe à 0 lorsqu’un résultat est non-nul. — N est le Negative bit. Il passe à 1 lorsque le résultat d’une opération est négatif (en complément à deux) et il passe à 0 lorsqu’un résultat est non-négatif. — C est le Carry bit. Il passe à 1 lorsqu’un calcul produit une retenue sortante, et il passe à 0 lorsqu’un calcul ne produit pas de retenue sortante. — V est le Overflow bit. Il est mis à 1 lorsque le résultat d’une opération arithmétique déborde de la fourchette des valeurs signées (en complément à deux), et à 0 sinon. La liste page ci-contre détaille l’effet de chaque instruction sur les quatre drapeaux : un tiret lorsque le drapeau n’est pas affecté, un 1 ou un 0 lorsque le drapeau passe toujours à une certaine valeur, et une étoile lorsque l’effet sur le drapeau dépend du résultat. Sauts conditionnels Les instructions de branchement sont de la forme JUMP label . Regardez par exemple le programme page 22. Le saut peut être soit inconditionnel (instruction JMP), soit soumis à une condition sur les drapeaux. Par exemple, l’instruction JNZ label est un Jump if Non-Zero : elle sautera vers label si et seulement si le bit Z est faux. Opérandes «word» ou «byte» Chaque instruction peut travailler sur des mots de 16 bits (par défaut), ou sur des octets (il faut pour cela remplacer OPCODE par OPCODE.B) . Par exemple, l’instruction MOV.B R10, &42 copie les 8 bits de poids faible de R10 vers l’octet situé à l’adresse 42, alors que l’instruction MOV R10, &42 copie tout le contenu de R10 vers les deux octets situés aux adresses 42 et 43 c . Reportez-vous à l’encadré page 46 pour plus de détails. a. Et donc en termes Unix c’est cp, pas mv. b. Si par mégarde on écrit mov 42, R5 au lieu d’écrire mov #42, R5 alors non seulement ça ne cause aucun message d’erreur, mais surtout le programme fera n’importe quoi. Vous voila prévenu. Et si vous voulez savoir ce qui se passe dans ce cas, assemblez puis désassemblez, puis cherchez dans la doc ce qu’on vous a caché. c. Précision : les 8 bits de poids faible vont en 42, et les 8 bits de poids fort vont en 43. On dit que le msp430 est de type little-endian. Allez lire https://fr.wikipedia.org/wiki/Endianness si c’est la première fois que vous voyez ce mot. 26 Liste compacte des instructions MSP430 Description Operation V N Z ADC(.B) dst Add C to destination dst + C → dst * * * * ADD(.B) src,dst Add source to destination src + dst → dst * * * * ADDC(.B) src,dst Add source and C to destination src + dst + C → dst * * * * AND(.B) src,dst AND source and destination src .and. dst → dst 0 * * * Mnemonic C BIC(.B) src,dst Clear bits in destination .not.src .and. dst → dst − − − − BIS(.B) src,dst Set bits in destination src .or. dst → dst − − − − BIT(.B) src,dst Test bits in destination src .and. dst 0 * * * BR dst Branch to destination dst → PC − − − − CALL dst Call destination PC+2 → stack, dst → PC − − − − CLR(.B) dst Clear destination 0 → dst − − − − 0 CLRC Clear C 0→C − − − CLRN Clear N 0→N − 0 − − CLRZ Clear Z 0→Z − − 0 − * CMP(.B) src,dst Compare source and destination dst − src * * * DADC(.B) dst Add C decimally to destination dst + C → dst (decimally) * * * * DADD(.B) src,dst Add source and C decimally to dst. src + dst + C → dst (decimally) * * * * * DEC(.B) dst Decrement destination dst − 1 → dst * * * DECD(.B) dst Double-decrement destination dst − 2 → dst * * * * Disable interrupts 0 → GIE − − − − − DINT Enable interrupts 1 → GIE − − − INC(.B) dst Increment destination dst +1 → dst * * * * INCD(.B) dst Double-increment destination dst+2 → dst * * * * .not.dst → dst EINT INV(.B) dst Invert destination JC/JHS label Jump if C set/Jump if higher or same JEQ/JZ label Jump if equal/Jump if Z set − − − − JGE label Jump if greater or equal − − − − − JL label Jump if less JMP label Jump JN label Jump if N set PC + 2 x offset → PC * * * * − − − − − − − − − − − − − − − JNC/JLO label Jump if C not set/Jump if lower − − − − JNE/JNZ label Jump if not equal/Jump if Z not set − − − − MOV(.B) src,dst Move source to destination − − − − − − − − − src → dst No operation NOP POP(.B) dst Pop item from stack to destination @SP → dst, SP+2 → SP − − − PUSH(.B) src Push source onto stack SP − 2 → SP, src → @SP − − − − Return from subroutine @SP → PC, SP + 2 → SP − − − − * RET Return from interrupt * * * RLA(.B) dst Rotate left arithmetically * * * * RLC(.B) dst Rotate left through C * * * * RRA(.B) dst Rotate right arithmetically 0 * * * RRC(.B) dst Rotate right through C * * * * SBC(.B) dst RETI Subtract not(C) from destination dst + 0FFFFh + C → dst * * * * SETC Set C 1→C − − − 1 SETN Set N 1→N − 1 − − SETZ Set Z 1→C − − 1 − * SUB(.B) src,dst Subtract source from destination dst + .not.src + 1 → dst * * * SUBC(.B) src,dst Subtract source and not(C) from dst. dst + .not.src + C → dst * * * * SWPB dst Swap bytes − − − − SXT dst Extend sign 0 * * * TST(.B) dst Test destination dst + 0FFFFh + 1 0 * * 1 XOR(.B) src,dst Exclusive OR source and destination src .xor. dst → dst * * * * Remarque chacune de ces instructions est documentée en détail dans la doc (msp430x4xx.pdf, section 3.4). Il faut s’y reporter si vous avez besoin de précisions. 27 6 Memory-mapped IO À savoir : Les entrées-sorties Du point de vue du processeur, un périphérique se présente comme un ensemble de registres (au sens du cours d’AC), qui permettent d’échanger de l’information entre le CPU et le périphérique. On peut distinguer informellement trois sortes de registres dans un périphérique : — les registres d’état du périphérique fournissent de l’information sur l’état du périphérique : est-il actif, est-il prêt, a-t-il quelquechose à dire, etc. Ils sont typiquement accessibles en lecture seulement : le processeur peut lire leur contenu, mais pas le modifier. — les registres de contrôle ou de configuration du périphérique sont utilisés par le CPU pour configurer et contrôler le périphérique. Ils seront typiquement accessibles en lecture-écriture, ou parfois en écriture seulement. — les registres de données du périphérique permettent de lui envoyer des données (en écrivant dedans depuis le CPU) ou de recevoir des données de la part d’un périphérique (en lisant dedans). Contrôleur de périphérique reg. d’état CPU reg. de configuration Périphérique (optionnel) Monde extérieur reg. de données Tout cela est assez informel. Dans certains cas, un même registre peut appartenir à plusieurs de ces catégories, par exemple s’il contient à la fois des informations d’état (en lecture seule) et des information de configuration (en lecture/écriture). La circuiterie contenant ces registres est appelée le contrôleur du périphérique. La plupart des boites sur la figure de la page 18 sont des contrôleurs de périphériques. Physiquement parlant, le contrôleur est parfois situé sur le périphérique lui-même, par exemple un contrôleur de disque dur. Parfois au contraire il est placée plus près du processeur (ceux de la page 18 sont tous intégrés sur la même puce). et reliée ensuite au périphérique proprement dit par un moyen quelconque. Par exemple, votre carte vidéo est reliée à votre écran par un câble VGA ou HDMI. L’architecture générique est illustrée ci-dessous : Les registres matériels doivent pouvoir être accédés individuellement par le CPU. Comme pour les cases mémoire, on leur donne donc chacun une adresse distincte. Certains processeurs distinguent les adresses de mémoire et les adresses de registres matériels ; ils offrent alors des instructions distinctes pour accéder aux uns et aux autres. À l’inverse, la majorité des processeurs, dont notre MSP430, utilisent un unique espace d’adressage : certaines adresses correspondent à de la mémoire, et d’autres à des registres matériels. Les entrées-sorties se font alors avec les mêmes instructions que les accès mémoire classiques. De plus, les contrôleurs de périphériques et la mémoire se partagent les mêmes bus d’adresse et de donnée : à nouveau, voir la figure de la page 18. On parle alors d’entrées/sorties «projetées en mémoire», ou Memory-Mapped Input/Output. 28 Utile pour le TP : le plan mémoire du msp430 Du point de vue du CPU, la mémoire principale et les périphériques se présentent tous comme des cases mémoire. Certains registres matériels font 16 bits, et occupent donc deux adresses consécutives (à gauche sur le schéma ci-dessous). Certains autres registres ne font que 8 bits, et occupent une seule adresse (à droite sur le schéma ci-dessous). Vous aurez aussi remarqué que la «mémoire» est elle-même composée d’une région de RAM (en lecture-écriture) et d’une région de mémoire flash (en lecture seule). Pour s’y retrouver, la documentation technique nous indique le «plan d’adressage» (en VO, la memory map) c’est à dire une cartographie des différentes régions de l’espace d’adressage de la machine : Address FFFFh Access Interrupt Vector Table Word/Byte Flash/ROM Word/Byte RAM Word/Byte Reserved No access FFC0h FFDFh FFBFh 3100h 30FFh 1100h 01FFh 16-Bit Peripheral Modules Word 8-Bit Peripheral Modules Byte Special Function Registers Byte 0100h 00FFh 0010h 000Fh 0000h Exercice 22 Pour allumer notre diode on écrivait aux adresses 50 et 49. Traduisez-les en hexa (de tête !) et placez-les sur le plan mémoire. Exercice 23 Cherchez la diode LED4 sur le schéma de la page 17. Comment s’appelle la broche du processeur auquel elle est reliée ? La partie intéressante commence par P (comme Port d’entrée-sortie). Exercice 24 Même question pour les deux autres diodes LED1 et LED2. Exercice 25 Entourez, parmi les périphériques de la figure de la page 18, ceux qui commandent ces diodes. Ces broches sont des general purpose input/outputs, ou GPIO. Allez lire l’introduction du chapitre 11 de msp430x4xx.pdf. Elles peuvent être configurés en entrée (I) ou en sortie (O). Ce choix se fait par écriture dans un registre de contrôle. Pour le port 5 ce registre s’appelle P5DIR et est mappé à l’adresse 50. Vous reconnaissez le 50 ? Ces registres font 8 bits, ainsi les GPIO vont par 8. Exercice 26 Retrouvez l’adresses de P5DIR dans le tableau de la page 413 de msp430x4xx.pdf. Qu’est-ce qui est mappé à l’adresse 49 ? Exercice 27 Vous avez à présent tout ce qu’il faut pour savoir comment allumer les deux autres diodes. Exercice 28 Le buzzer (cherchez-le sur les schémas) se commande comme une diode : pour jouer une note, il suffit de le faire “clignoter” à la bonne fréquence. Essayez de jouer une note. Vous pouvez faire monter et descendre la note pour faire des bruits de sirènes... faites un concours de sons et lumières. 29 7 S’il reste du temps Exercice 29 Écrivez (et testez) un programme qui lit un argument dans R15, et le sort en binaire sur nos deux diodes : une diode clignote comme une horloge, et une autre diode s’allume pour les bits à 1 (en commençant par les bits de poids faible). Exercice 30 Écrivez une procédure qui divise R14 par R15 et renvoie le résultat dans R15. 30 Partie IV: MSP430 - Convention d’appel Dans cette partie, on va s’intéresser aux conventions d’appel, c’est à dire à la façon dont les différentes fonctions d’un programme communiquent entre elles. Créez un nouveau répertoire TP3 et mettez tous vos nouveaux fichiers là-dedans. À savoir : le rôle de la convention d’appel Lorsqu’on se situe au niveau assembleur, le langage reflète directement l’architecture de la machine : registres, instructions, etc. On ne retrouve donc pas les notions classiques des langages de programmation : variables, boucles, conditionnelles, qu’il faut implémenter explicitement en utilisant le jeu d’instructions disponibles. Pour les appels de fonction (aka «procédure», «méthode», «sous-programme», «routine») la plupart des architectures offrent des instructions dédiées. Ces instructions s’appellent par exemple CALL et RET sur msp430 (et sur x86), ou BL et BX sur ARM. Ainsi, CALL #func saute vers la fonction func, et RET retourne vers la fonction appelante. Mais ces instructions ne s’occupent pas particulièrement des arguments ni des résultats. Afin que nos différentes fonctions puissent communiquer entre elles, il faut donc se mettre d’accord, entre appelant et appelé, sur la façon de se passer les paramètres et de rendre les résultats : c’est la convention d’appel. 1 Appels de fonctions en assembleur Exercice 31 Recopiez le programme ci-dessous dans un fichier tp3.s. Identifiez dans le code machine, les zones qui correspondent aux différentes fonctions. .section .init9 main: /* emballage des arguments */ MOV #6, R5 MOV #7, R6 call #mult /* Le resultat est suppose retourne dans le registre R7 */ MOV R7, R15 /* infinite loop */ done: jmp done mult: MOV #0, R7 ret Rappel : la chaîne de compilation Vous avez déjà vu toutes les commandes utiles dans les TP précédents : — Pour assembler un programme ASM vers du langage machine : msp430-as -mmcu=msp430fg4618 -o truc.o truc.s — Pour faire l’édition de liens entre un ou plusieurs modules et obtenir un exécutable : msp430-gcc -mmcu=msp430fg4618 -mdisable-watchdog -o bidule.elf truc1.o truc2.o — Pour désassembler un exécutable et obtenir un fichier texte lisible à l’oeil nu : msp430-objdump -d machin.elf > machin.lst 31 Exercice 32 On veut maintenant que la fonction mult calcule la multiplication a × b. Écrivez le code nécessaire pour additionner a sur lui-même b fois. Notre convention d’appel sera la suivante : — à l’entrée dans la fonction, R5 et R6 contiennent les arguments, qui sont supposés strictement positifs et “pas trop grands” : on suppose que a et b sont assez petits pour que a × b tienne sur 16 bits. — au retour de la fonction, R7 contient la valeur de retour. Débuggez ! Puis, faites valider par un enseignant. 2 Étude du fonctionnement détaillé de call et ret Dans cette partie, on s’intéresse un peu plus en détail à ce qui se passe dans le processeur au moment de l’exécution des instructions call et ret. Commencez par lire l’extrait de documentation ci-dessous qui parle de la pile. En particulier vous devez en déduire que le registre R1 a un rôle particulier et qu’il doit être manipulé avec beaucoup de précaution : en particulier vous ne devez jamais vous en servir pour garder des résultats intermédiaires de vos calculs. Exercice 33 Reprenez votre programme main précédent et, avec le débuggeur, placez-vous juste avant l’instruction call #mult. Notez la valeur contenue dans le registre SP. Effectuez le saut (step dans le débuggeur). Notez la nouvelle valeur contenue dans SP. Observez la mémoire à ces deux adresses et comprenez ce qui y est stockée. En particulier, comparez ces valeurs avec les adresses des instructions autour du call. Exercice 34 Continuez d’exécuter votre programme jusqu’à atteindre l’instruction ret. Observez de nouveau la mémoire autour de l’adresse contenue dans SP avant et après le ret. Exercice 35 Recopiez le programme ci-dessous dans un nouveau fichier. Recopiez également votre version mult. Et surtout implémentez la routine sumprod telle que spécifiée. .section .init9 main: /* Ici, il y a du travail */ /* TODO: preparer les parametres de sumprod */ call #sumprod mov r14,r15 /* sumprod(v1,c1,v2,c2) computes v1*c1+v2*c2. inputs: in r10,r11,r12,r13 respectively outputs: in r14 note: uses mult :) */ sumprod: /* Ici, il y a du travail! */ /* TODO: realisez la fonction attendue en vous servant de mult */ mov #0, r14 ret /* mult(a,b) computes a*b inputs: r5,r6 outputs: r7 notes: Votre version copiee-colee ici ! */ mult: mov #0, r7 ret Exercice 36 A l’aide du débuggeur, suivez l’évolution des adresses contenue dans SP et de la zone mémoire correspondante. En particulier, comprenez ce qui s’est passé dans la pile lorsque vous êtes en train d’exécuter le deuxième appel à mult. 32 Extrait de la documentation : msp430x4xx.pdf page 45 3.2.2 CPU Registers Stack Pointer (SP) The stack pointer (SP/R1) is used by the CPU to store the return addresses of subroutine calls and interrupts. It uses a predecrement, postincrement scheme. In addition, the SP can be used by software with all instructions and addressing modes. Figure 3−3 shows the SP. The SP is initialized into RAM by the user, and is aligned to even addresses. Figure 3−4 shows stack usage. Figure 3−3. Stack Pointer 15 1 0 Stack Pointer Bits 15 to 1 MOV MOV PUSH POP 2(SP),R6 R7,0(SP) #0123h R8 ; ; ; ; 0 Item I2 −> R6 Overwrite TOS with R7 Put 0123h onto TOS R8 = 0123h Figure 3−4. Stack Usage Address PUSH #0123h POP R8 0xxxh I1 I1 I1 0xxxh − 2 I2 I2 I2 0xxxh − 4 I3 I3 I3 SP SP 0123h 0xxxh − 6 SP 0123h 0xxxh − 8 The special cases of using the SP as an argument to the PUSH and POP instructions are described and shown in Figure 3−5. Figure 3−5. PUSH SP - POP SP Sequence Extrait de la documentation : msp430x4xx.pdf page 69 PUSH SP POP SP SPold SP1 SP1 The stack pointer is changed after a PUSH SP instruction. SP2 SP1 The stack pointer is not changed after a POP SP instruction. The POP SP instruction places SP1 into the stack pointer SP (SP2=SP1) RISC 16-Bit CPU 33 3-5 Extrait de la documentation : msp430x4xx.pdf page XX 34 Partie V: MSP430 - Interruptions L’objectif de cette est d’étudier un rouage essentiel dans le fonctionnement des ordinateurs, le mécanisme des interruptions matérielles. Après avoir vu le coté implémentation matérielle lors du TP micro-machine, nous allons étudiez les aspects logiciels liés. En particulier, on va s’intéresser aux notions suivantes : requête d’interruption (IRQ), vecteur d’interruption, masquage d’interruption, routine de traitement d’interruption (ISR), sauvegarde de contexte, acquittement d’interruption. Mais avant ça, on va prendre en main les bouttons, sans parler d’interruption. 1 Boutons et attente active Exercice 37 Sur la carte, trouvez à quelles broches du processeur (et donc quel prot GPIO) les deux boutons sont connectés. Exercice 38 Recopiez le bout de code suivant dans un nouveau fichier. .section .init9 main: /* Ici, il y a du travail */ /* On veut configurer les bons ports en mode ‘‘entree’’*/ /* Et on n’oublie pas de configurer les bons ports pour pouvoir allumer la LED1 */ loop: /* Puis on veut attendre que l’un des boutons soit appuye*/ /* /* /* /* Lorsqu’un appui a eu lieu, on veut : */ - allumer LED1 si elle etait eteinte ou */ - l’inverse */ nb: rappelez-vous que ca peut se faire en une seule instruction :) ! */ /* on recommence */ jmp loop Exercice 39 Commencez par configurer le port que vous avez identifié à la question précédente pour qu’il agisse comme une entrée. Exercice 40 Maintenant écrivez le bout de programme qui vous permet d’attendre que le bouton soit appuyé. Commencez par l’écrire en pseudo-c, puis traduisez-le en assembleur. Exercice 41 Bien sûr, n’oubliez pas de complétez à la fois la configuration et le code de la boucle principale pour faire clignoter la led en alternant son état à chaque fois que vous appuyez sur le bouton. 2 Boutons et interruptions Un inconvénient majeur de ce qu’on a fait jusqu’ici c’est que le processeur est entièrement occupé à attendre que l’on appuie sur un bouton. C’est cela qu’on appelle attente active (polling en langue de Shakespeare). Pour remedier à ce problème, on peut configer le processeur pour qu’il réagisse aux appuis boutons à travers son mécanisme d’interruption. Entre les appuis boutons, le processeur va ainsi être libre d’exécuter autre chose ! Dans un premier temps, lisez les deux encadrés ci-dessous, pour vous rafraîchir la mémoire sur les différentes notions mises en jeu et pour commencer à comprendre comment les interruptions sont implémentées matériellement dans le cas du msp430 et de notre carte. À savoir : scrutation VS interruptions, requêtes (IRQ), vecteur, routine de traitement (ISR) La communication entre un périphérique et le processeur se fait en général au travers des registres matériels. Un composant qui veut transmettre une information au programme place cette information dans un de ses registres, et attend que le processeur vienne lire cette valeur. C’est cette technique, appelée scrutation (en anglais polling) que vous avez utilisée dans un TP précédent pour connaître l’état des 35 boutons. Un inconvénient majeur de cette approche est son incapacité à passer à l’échelle : pour ne pas rater un évènement, le programme doit continuellement aller scruter l’état du matériel, ce qui monopolise le processeur. L’alternative consiste à mettre en place un mécanisme d’interruptions (cf poly page 73). Dans ce cas, le composant qui veut transmettre une information au programme place cette information dans l’un de ses registres, puis envoie au processeur une requête d’interruption (en anglais interrupt request ou IRQ). Selon les architectures, ces requêtes peuvent transiter sur le bus principal ou sur des fils dédiés appelés des lignes d’interruptions. Dans ce cas-là, soit le processeur dispose d’une entrée pour chaque source d’interruptions, soit comme illustré ci-dessous, les lignes d’interruptions sont concentrées par un périphérique dédié, appelé le contrôleur d’interruption. IRQ CPU Interrupt controller Periph Periph system bus Periph Periph Periph Du côté du processeur, la gestion des interruptions est intégrée au cycle de Von Neumann. Lorsqu’il reçoit une requête, le processeur interrompt automatiquement l’exécution du programme et saute vers une adresse bien connue, à laquelle il s’attend à trouver une routine de traitement spécifique (en anglais interrupt service routine ISR, ou interrupt handler ). Chaque ligne d’interruption est ainsi associée à une routine distincte, ce qui permet au programmeur de prévoir un traitement différent pour chaque type d’évènement. La correspondance {requête n1 → routine r1 , requête n2 → routine r2 , etc.} est implémentée par une structure de données appelée la table des vecteurs d’interruption (interrupt vector table ou IVT). En général il s’agit d’un tableau de pointeurs de fonction, chaque case contenant l’adresse d’une ISR. Une routine d’interruption est un morceau de code similaire à une fonction, sauf qu’elle est invoquée automatiquement par le processeur, et non pas par un appel explicite. En plus de traiter l’évènement proprement dit en allant lire et/ou écrire dans les registres du périphérique concerné, une ISR devra typiquement accuser réception de l’interruption auprès du périphérique et/ou du contrôleur d’interruption. Ensuite, elle peut se terminer et restaurer le contexte d’exécution, c’est à dire reprendre l’exécution du programme interrompu, qui ne se sera aperçu de rien. 36 Utile pour le TP : les interruptions sur le MSP430 Le fonctionnement des interruptions est détaillé au chapitre 2.2 de la documentation (msp430x4xx.pdf pages 29 et suivantes) et nous en reprenons les grandes lignes ici. Le CPU du MSP430 ne dispose pas d’une ligne d’interruption distincte pour chaque périphérique, mais d’une seule ligne partagée par tous les périphériques. Un composant (par exemple le Module 2) qui veut lever une interruption fait passer son interrupt flag à 1 (dans notre exemple, il s’agit donc du bit M2IFG). Certains modules ont plusieurs drapeaux, mais le fonctionnement reste similaire (les petits carrés noirs du schéma représentent des bits accessibles en mémoire dans un registre matériel). Le signal traverse les autres périphériques et atteint le processeur. Celui-ci perçoit donc une requête d’interruption (IRQ) lorsque : — au moins un des périphériques a un interrupt flag levé, — et le bit GIE du registre SR est vrai (bit Global Interrupt Enable du Status Register ) Interrupt Priority IRQ High GIE CPU Low Module 1 Module 2 Module 3 M3IFG1 M3IFG2 M2IFG M1IFG Module X MXIFG Module Y MYIFG Bus Grant MAB − 5LSBs Pour savoir de qui vient la requête, le processeur passe alors le signal Bus Grant à 1. Mais il se peut que plusieurs périphériques aient des flags levés, et dans ce cas-là on veut les départager par ordre de priorité. Chaque module, grâce à la circuiterie illustrée ci-dessous, émet donc son propre numéro si et seulement si : — ce module a une interrution en attente (i.e. son interrupt flag est levé), — et aucun module plus prioritaire n’a d’interruption en attente (cf fil de gauche sur le schéma), — et le signal Bus Grant venant du CPU est actif (cf fil venant du bas). Il y a donc bien un et un seul numéro d’IRQ envoyé au processeur (dans notre exemple, le nombre 2 codé sur cinq bits : 0b00010). Module X IRQ num = X MXIFG 5 5 MAB Bus Grant Lorsqu’il reçoit ce numéro, le CPU l’utilise comme indice dans la table des vecteurs, et charge le vecteur dans PC, ce qui revient à sauter à l’adresse de l’ISR.La table est située en mémoire flash à l’adresse 0xFFC0, donc le vecteur numéro x est le mot d’adresse 0xFFC0+2x. Exercice 42 En vous aidant de l’encadré de la présente page, configurez le port P1 pour qu’il génère une interruption lors de l’appui sur un bouton. Remarque : tant que cette interruption ne sera pas rattrapée correctement (vers la question 50), vous ne pourrez pas tester par vous-même que vos réponses sont 37 correctes. Du coup, soit vous comprenez ce que vous faites et vous avancez jusqu’à la question 50, soit vous avez du mal et alors demandez-nous de l’aide pour vous débloquer. Mais ne perdez pas trop de temps à patauger. Exercice 43 Écrivez une routine qui inverse l’état de la LED. Utilisez une étiquette temporaire temp_isr:. Nous verrons plus loin par quoi la remplacer. Il reste maintenant à “brancher” ce traitant à la requête d’interruption que vous avez configuré juste avant. 3 Interruption sur bouton poussoir La première chose à faire consiste à faire en sorte que lorsqu’on appuie sur un des boutons, une interruption soit émise vers le processeur. Exercice 44 Commencez par lire l’encadré ci-dessous, qui parle de la gestion des interruptions sur le port P1. 38 Extrait de la documentation : msp430x4xx.pdf pages 411 et 412 Digital I/O Operation 11.2.6 P1 and P2 Interrupts Each pin in ports P1 and P2 have interrupt capability, configured with the PxIFG, PxIE, and PxIES registers. All P1 pins source a single interrupt vector, and all P2 pins source a different single interrupt vector. The PxIFG register can be tested to determine the source of a P1 or P2 interrupt. Interrupt Flag Registers P1IFG, P2IFG Each PxIFGx bit is the interrupt flag for its corresponding I/O pin and is set when the selected input signal edge occurs at the pin. All PxIFGx interrupt flags request an interrupt when their corresponding PxIE bit and the GIE bit are set. Each PxIFG flag must be reset with software. Software can also set each PxIFG flag, providing a way to generate a software-initiated interrupt. Bit = 0: No interrupt is pending Bit = 1: An interrupt is pending Only transitions, not static levels, cause interrupts. If any PxIFGx flag becomes set during a Px interrupt service routine or is set after the RETI instruction of a Px interrupt service routine is executed, the set PxIFGx flag generates another interrupt. This ensures that each transition is acknowledged. Note: PxIFG Flags When Changing PxOUT or PxDIR Writing to P1OUT, P1DIR, P2OUT, or P2DIR can result in setting the corresponding P1IFG or P2IFG flags. Note: Digital I/O Operation Length of I/O Pin Interrupt Event Any external interrupt event should be at least 1.5 times MCLK or longer, to ensure that it is accepted and the corresponding interrupt flag is set. Interrupt Edge Select Registers P1IES, P2IES Each PxIES bit selects the interrupt edge for the corresponding I/O pin. Bit = 0: The PxIFGx flag is set with a low-to-high transition Bit = 1: The PxIFGx flag is set with a high-to-low transition Note: Writing to PxIESx Writing to P1IES or P2IES can result in setting the corresponding interrupt flags. PxIESx 0→1 0→1 1→0 1→0 PxINx 0 1 0 1 PxIFGx May be set Unchanged Unchanged May be set Interrupt Enable P1IE, P2IE Digital I/O Each PxIE bit enables the associated PxIFG interrupt flag. Bit = 0: The interrupt is disabled Bit = 1: The interrupt is enabled 11.2.7 Configuring Unused Port Pins 39 11-5 Exercice 45 Dans votre programme, rajouter les instructions nécessaires pour activer les interruptions du port 1. Rappel, les addresses auxquel les registres permettant de configurer P1 sont données à la page 413 de la documentation msp430x4xx. Exercice 46 Par défaut, le processeur n’entend pas les interruptions. Il faut donc activer les interruptions explicitement. Le processeur fournit une instruction pour ça, eint. Allez vérifier son comportement et sa syntaxe, page 80 de la doc. msp430x4xx. Il reste maintenant à étiquetter convenablement votre traitant d’interruption pour que le compilateur l’associe au bon vecteur d’interruption. Exercice 47 En vous aidant de l’encadré page précédente et de l’extrait de configuration ci-dessous, déterminez le numéro du vecteur d’interruption associé à P1. Extrait des fichiers de configuration : msp430fg4618.h /************************************************************ * Interrupt Vectors (offset from 0xFFC0) ************************************************************/ #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define DAC12_VECTOR DMA_VECTOR BASICTIMER_VECTOR PORT2_VECTOR USART1TX_VECTOR USART1RX_VECTOR PORT1_VECTOR TIMERA1_VECTOR TIMERA0_VECTOR ADC12_VECTOR USCIAB0TX_VECTOR USCIAB0RX_VECTOR WDT_VECTOR COMPARATORA_VECTOR TIMERB1_VECTOR TIMERB0_VECTOR NMI_VECTOR RESET_VECTOR (0x001C) (0x001E) (0x0020) (0x0022) (0x0024) (0x0026) (0x0028) (0x002A) (0x002C) (0x002E) (0x0030) (0x0032) (0x0034) (0x0036) (0x0038) (0x003A) (0x003C) (0x003E) /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* /* 0xFFDC 0xFFDE 0xFFE0 0xFFE2 0xFFE4 0xFFE6 0xFFE8 0xFFEA 0xFFEC 0xFFEE 0xFFF0 0xFFF2 0xFFF4 0xFFF6 0xFFF8 0xFFFA 0xFFFC 0xFFFE DAC 12 */ DMA */ Basic Timer / RTC */ Port 2 */ USART 1 Transmit */ USART 1 Receive */ Port 1 */ Timer A CC1-2, TA */ Timer A CC0 */ ADC */ USCI A0/B0 Transmit */ USCI A0/B0 Receive */ Watchdog Timer */ Comparator A */ Timer B CC1-2, TB */ Timer B CC0 */ Non-maskable */ Reset [Highest Priority] */ Exercice 48 Soit xx le numéro que vous venez de déterminer. Vous pouvez maintenant remplacer l’étiquette temp_isr par les deux lignes suivantes : .global __isr_xx __isr_xx: Bien sûr, vous remplacez xx par la valeur trouvée à la question précédente. Exercice 49 Il faut maintenant faire en sorte que votre traitant d’interruption acquite l’interruption. Pour ça, relisez la partie concernant P1IFG dans l’encadré de la page page précédente. Exercice 50 Avez-vous pensé au retour du traitant d’interruption ? Sinon (ou si vous avez utilisé l’instruction RET) alors allez vite lire l’encadré ci-dessous et corrigez votre programme de façon adéquat. 40 Extrait de la documentation : msp430x4xx.pdf pages 97 Exercice 51 Dans la boucle principale de votre programme, faites de nouveau clignoter une diode (ou mieux : jouez de la musique sur le buzzer) et constatez avec satisfaction que les deux activités (programme principal et ISR) s’exécutent maintenant en bonne harmonie. 41 Annexe : Morceaux choisis de la documentation de l’assembleur MSP430 Extrait de la documentation : msp430x4xx.pdf page 44 CPU Registers 3.2 CPU Registers The CPU incorporates sixteen 16-bit registers. R0, R1, R2 and R3 have dedicated functions. R4 to R15 are working registers for general use. 3.2.1 Program Counter (PC) The 16-bit program counter (PC/R0) points to the next instruction to be executed. Each instruction uses an even number of bytes (two, four, or six), and the PC is incremented accordingly. Instruction accesses in the 64-KB address space are performed on word boundaries, and the PC is aligned to even addresses. Figure 3−2 shows the program counter. Figure 3−2. Program Counter 15 1 Program Counter Bits 15 to 1 0 0 The PC can be addressed with all instructions and addressing modes. A few examples: MOV MOV MOV #LABEL,PC ; Branch to address LABEL LABEL,PC ; Branch to address contained in LABEL @R14,PC ; Branch indirect to address in R14 42 Extrait de la documentation : msp430x4xx.pdf page 45 CPU Registers 3.2.2 Stack Pointer (SP) The stack pointer (SP/R1) is used by the CPU to store the return addresses of subroutine calls and interrupts. It uses a predecrement, postincrement scheme. In addition, the SP can be used by software with all instructions and addressing modes. Figure 3−3 shows the SP. The SP is initialized into RAM by the user, and is aligned to even addresses. Figure 3−4 shows stack usage. Figure 3−3. Stack Pointer 15 1 0 Stack Pointer Bits 15 to 1 MOV MOV PUSH POP 2(SP),R6 R7,0(SP) #0123h R8 ; ; ; ; 0 Item I2 −> R6 Overwrite TOS with R7 Put 0123h onto TOS R8 = 0123h Figure 3−4. Stack Usage Address PUSH #0123h POP R8 0xxxh I1 I1 I1 0xxxh − 2 I2 I2 I2 0xxxh − 4 I3 I3 I3 SP SP 0123h 0xxxh − 6 SP 0123h 0xxxh − 8 The special cases of using the SP as an argument to the PUSH and POP instructions are described and shown in Figure 3−5. Figure 3−5. PUSH SP - POP SP Sequence PUSH SP POP SP SPold SP1 SP1 The stack pointer is changed after a PUSH SP instruction. SP2 SP1 The stack pointer is not changed after a POP SP instruction. The POP SP instruction places SP1 into the stack pointer SP (SP2=SP1) RISC 16-Bit CPU 43 3-5 Extrait de la documentation : msp430x4xx.pdf page 46 CPU Registers 3.2.3 Status Register (SR) The status register (SR/R2), used as a source or destination register, can be used in the register mode only addressed with word instructions. The remaining combinations of addressing modes are used to support the constant generator. Figure 3−6 shows the SR bits. Figure 3−6. Status Register Bits 15 9 8 V Reserved 7 SCG1 0 OSC CPU SCG0 GIE OFF OFF N Z C rw-0 Table 3−1 describes the status register bits. Table 3−1.Description of Status Register Bits 3-6 Bit Description V Overflow bit. This bit is set when the result of an arithmetic operation overflows the signed-variable range. ADD(.B),ADDC(.B) Set when: Positive + Positive = Negative Negative + Negative = Positive, otherwise reset SUB(.B),SUBC(.B),CMP(.B) Set when: Positive − Negative = Negative Negative − Positive = Positive, otherwise reset SCG1 System clock generator 1. This bit, when set, turns off the DCO dc generator, if DCOCLK is not used for MCLK or SMCLK. SCG0 System clock generator 0. This bit, when set, turns off the FLL+ loop control OSCOFF Oscillator Off. This bit, when set, turns off the LFXT1 crystal oscillator, when LFXT1CLK is not use for MCLK or SMCLK CPUOFF CPU off. This bit, when set, turns off the CPU. GIE General interrupt enable. This bit, when set, enables maskable interrupts. When reset, all maskable interrupts are disabled. N Negative bit. This bit is set when the result of a byte or word operation is negative and cleared when the result is not negative. Word operation: N is set to the value of bit 15 of the result Byte operation: N is set to the value of bit 7 of the result Z Zero bit. This bit is set when the result of a byte or word operation is 0 and cleared when the result is not 0. C Carry bit. This bit is set when the result of a byte or word operation produced a carry and cleared when no carry occurred. RISC 16-Bit CPU 44 Extrait de la documentation : msp430x4xx.pdf page 47 CPU Registers 3.2.4 Constant Generator Registers CG1 and CG2 Six commonly-used constants are generated with the constant generator registers R2 and R3, without requiring an additional 16-bit word of program code. The constants are selected with the source-register addressing modes (As), as described in Table 3−2. Table 3−2.Values of Constant Generators CG1, CG2 Register As Constant Remarks R2 00 −−−−− Register mode R2 01 (0) Absolute address mode R2 10 00004h +4, bit processing R2 11 00008h +8, bit processing R3 00 00000h 0, word processing R3 01 00001h +1 R3 10 00002h +2, bit processing R3 11 0FFFFh −1, word processing The constant generator advantages are: ! No special instructions required ! No additional code word for the six constants ! No code memory access required to retrieve the constant The assembler uses the constant generator automatically if one of the six constants is used as an immediate source operand. Registers R2 and R3, used in the constant mode, cannot be addressed explicitly; they act as source-only registers. Constant Generator − Expanded Instruction Set The RISC instruction set of the MSP430 has only 27 instructions. However, the constant generator allows the MSP430 assembler to support 24 additional, emulated instructions. For example, the single-operand instruction: CLR dst is emulated by the double-operand instruction with the same length: MOV R3,dst where the #0 is replaced by the assembler, and R3 is used with As = 00. INC dst is replaced by: ADD 0(R3),dst RISC 16-Bit CPU 45 3-7 Extrait de la documentation : msp430x4xx.pdf page 48 CPU Registers 3.2.5 General-Purpose Registers R4 to R15 Twelve registers, R4 to R15, are general-purpose registers. All of these registers can be used as data registers, address pointers, or index values, and they can be accessed with byte or word instructions as shown in Figure 3−7. Figure 3−7. Register-Byte/Byte-Register Operations Register-Byte Operation High Byte Byte-Register Operation Low Byte Unused High Byte Byte Register Byte Memory Example Byte-Register Operation R5 = 0A28Fh R5 = 01202h R6 = 0203h R6 = 0223h Mem(0203h) = 012h Mem(0223h) = 05Fh ADD.B ADD.B R5,0(R6) Memory Register 0h Example Register-Byte Operation 08Fh @R6,R5 05Fh + 012h + 002h 0A1h 00061h Mem (0203h) = 0A1h R5 = 00061h C = 0, Z = 0, N = 1 C = 0, Z = 0, N = 0 (Low byte of register) 3-8 Low Byte (Addressed byte) + (Addressed byte) + (Low byte of register) −>(Addressed byte) −>(Low byte of register, zero to High byte) RISC 16-Bit CPU 46