Réalisation et validation d'un "nano-processeur" dans un CLP
Au cours de ce TP vous allez :
- étudier l'architecture du microprocesseur abordé dans la leçon-TD L7
- concevoir le compteur de programme (ou compteur d'adresse ou PC) du microprocesseur ;
- concevoir la machine à état qui contrôle le processeur
- configurer un circuit logique programmable ALTERA afin qu'il réalise ce microprocesseur ;
- faire exécuter un programme musical à votre microprocesseur sur une carte de test ;
- de manière libre : améliorer votre microprocesseur en enrichissant son jeu d'instructions...
Conseils :
- suivez les conseils
- un manuel d'utilisation ALTERA est disponible auprès de chaque poste de travail
- respectez scrupuleusement les noms de composants et d'entrées-sorties proposés
- pour les machines à états utilisez le codage d'états proposé par le sujet
- utilisez la vectorisation et la connexion par nom pour alléger vos schémas
- vous pouvez utiliser la cellule wire pour donner deux noms différents au même noeud d'un schéma
- vérifiez toujours que le nom du projet courant est celui du fichier que vous éditez
- ne tracez jamais de fil au dessus d'un composant, pas même le long du bord extérieur
- ne placez jamais un composant par dessus un autre composant
- vous pouvez modifier les symboles générés automatiquement pour simplifier votre schéma
- ne perdez pas de temps à embellir votre schéma
1. Architecture du système à microprocesseur
Bien que le sujet du TP concerne plus le coeur d'exécution du microprocesseur que son environnement d'utilisation, nous présentons ici le système complet implanté sur la maquette de test. Le système proposé se compose :
- Du microprocesseur implanté dans un circuit logique programmable FLEX10K30 ;
- Dune mémoire RAM contenant le programme et les données (cette mémoire est elle aussi intégrée dans le FLEX10K30) ;
- D'un haut-parleur piloté par un port de sortie du microprocesseur.

Architecture du système total : processeur + RAM + buzzer
Remarque : il y a quelques différences par rapport à la leçon sur le microprocesseur portant sur les noms des signaux :
-
le bus d'adresse de la RAM n'est pas appelé ADDDRESS[7:0] mais ADR[7:0]
-
le bus d'entrée de la RAM n'est pas appelé D[7:0] mais A[7:0]
-
le bus de sortie de la RAM n'est pas appelé Q[7:0] mais D[7:0]
Seuls 2 signaux externes pilotent le système :
- l'horloge CLK
- un signal de remise a zero NRESET
| Nom |
Entrées et sorties du processeur |
Type |
| CLK |
Horloge générale |
Entrée |
| NRESET |
Remise à zéro asynchrone et active à '0' |
Entrée |
| BZ |
Port de sortie pouvant être positionné à '1' ou '0' |
Sortie |
| ADR[7..0] |
Adresse lecture/écriture dans la RAM sortie |
Sorties |
| A[7..0] |
Sortie de l'ACCUmulateur.
Ce mot est mémorisé dans la RAM à l'adresse ADR
(accès de la RAM en écriture) |
Sorties |
| D[7..0] |
Mot lu dans la RAM à l'adresse ADR
(accès de la RAM en lecture) |
Entrées |
| WRITE |
Signal indiquant le sens de l'échange
avec la mémoire ('1' pour une écriture) |
Sortie |
2. Fonctionnement et jeu d'instructions du microprocesseur
Les caractéristiques principales de notre microprocesseur sont les suivantes :
- Le jeu d'instructions du microprocesseur se limite à 16 instructions.
- Chaque instruction est codée sur deux octets :
- Le premier octet contient le code de l'opération proprement dite ;
- Le deuxième octet contient un pointeur qui donne l'adresse en mémoire d'une éventuelle opérande.
- L'adresse de l'accès courant (à une instruction) en RAM est notée PC ("Program Counter").
- La mémoire adressable par le microprocesseur ne dépasse pas 256 octets (8 bits d'adresse)
- Les données traitées sont des entiers naturels limités à l'intervalle 0..255 (8 bits de données)
- L'opérateur de calcul du microprocesseur se limite à quelques opérations logiques et arithmétiques simples (voir tableau).
Le résultat de l'opération effectué est stocké dans l'accumulateur.
- Lors d'une modification de l'accumulateur, deux signaux supplémentaires sont générés et mémorisés :
Z si ce résultat est nul, et C si une retenue sortante existe.
Le tableau suivant résume le jeu d'instruction du microprocesseur :
| Mnémonique |
Instruction |
Effet sur l'accumulateur |
Effet sur le PC |
code (binaire 8 bits) |
| NOP |
No OPeration |
A <= A |
PC <= PC + 2 |
00000000 |
| XOR |
XOR function |
A <= A xor (AD) |
PC <= PC + 2 |
00000001 |
| AND |
AND function |
A <= A and (AD) |
PC <= PC + 2 |
00000010 |
| OR |
OR function |
A <= A or (AD) |
PC <= PC + 2 |
00000011 |
| ADD |
ADDition without carry |
A <= A + (AD) |
PC <= PC + 2 |
00000100 |
| ADC |
ADDition with Carry |
A <= A + (AD) + C |
PC <= PC + 2 |
00000101 |
| SUB |
SUBstraction without carry |
A <= A - (AD) |
PC <= PC + 2 |
00000110 |
| SBC |
SuBstraction with Carry |
A <= A - (AD) - C |
PC <= PC + 2 |
00000111 |
| ROL |
ROtateLeft |
A <= A[6..0]A[7] |
PC <= PC + 2 |
00001000 |
| ROR |
ROtateRigth |
A <= A[0]A[7..1] |
PC <= PC + 2 |
00001001 |
| LDA |
LoaD Accumulator from memory |
A <= (AD) |
PC <= PC + 2 |
00001010 |
| STA |
STore Accumulator to memory |
(AD) = A |
PC <= PC + 2 |
00001011 |
| OUT |
OUTput |
BZ <= (AD)(0) |
PC <= PC + 2 |
00001100 |
| JMP |
JuMP |
A <= A |
PC <= AD |
00001101 |
| JNC |
Jump if No Carry |
A <= A |
PC <= AD si C=0,
PC+2 sinon |
00001110 |
| JNZ |
Jump if No Zero |
A <= A |
PC <= AD si Z=0,
PC+2 sinon |
00001111 |
- L'expression (AD) représente le contenu de la mémoire à l'adresse AD fournie par le deuxième octet de l'instruction
(AD représente cette adresse, et (AD) le contenu de cette adresse)
- L'expression PC <= PC + 2 indique que la valeur du pointeur programme, pour l'instruction suivante, sera la valeur courante incrémentée de 2.
- L'expression A[6..0] indique les 7 bits de poids faibles du registre A.
De même, (AD)[0] correspond au bit de poids 0 du contenu de la mémoire à l'adresse AD.
Nous vous conseillons de vous familiariser avec le jeu d'instructions et les codes associés du microprocesseur en étudiant le programme verif_globale qui servira de jeu de test de simulation pour le microprocesseur.
3. Architecture du coeur du processeur
Le coeur est composé de quelques registres, d'une unité de calcul, d'un compteur de programme et d'une unité de contrôle. Tous les registres du microprocesseur sont pilotés par l'horloge CLK et remis à zéro par l'état bas du signal NRESET.

Architecture du processeur
Remarque : le signal SEL_ACC, avec le multiplexeur qu'il commande est interne à l'ALU, son équation étant SEL_ACC = (I[7:0] == "LDA")...
Le compteur de programme (PC)
Le compteur de programme (8 bits) sert à stocker l'adresse de l'instruction courante.
Son mode "standard" de fonctionnement est de s'auto incrémenter lors des phases IF et AF pour aller chercher en séquence les différents octets des instructions à exécuter.
En cas d'instruction saut, il est directement chargé, lors de la phase AF avec le contenu du bus de donné D(7..0), sous le contrôle du signal LOAD_PC.
Le signal LOAD_PC est prioritaire par rapport au signal INC_PC.
La machine à état de contrôle (CTR)
Cette unité contient la machine à état permettant de gérer les unités du processeur.
Elle est donc en charge de séquencer les différents cycles d'une instruction (chargement d'une instruction, chargement de l'opérande, puis exécution) et de générer les signaux de contrôle des différentes blocs, sans oublier, bien sûr, le signal WRITE de validation d'écriture en mémoire.
Registres de mémorisation (REG dans le schéma MaxPLusII)
Ce bloc regroupe en fait les quatre registres suivants :
- Le registre de sortie pour le buzzer.
- Le registre d'accumulation.
- Le registre d'instruction
- Le registre d'adresse
Tous ces registres fonctionnent sur le même mode : l'entrée X est mémorisée dans le registre sur le front montant de l'horloge si l'enable correspondant (LOAD_X) est à 1.
Le multiplexeur d'adresses (MUX_ADDR, en sortie du PC)
Ce multiplexeur permet de choisir qui, de l'adresse "pointeur programme" PC ou de l'adresse de données AD, doit être envoyé sur le bus d'adresse ADR de la mémoire. Le choix de l'un ou de l'autre dépend de l'état courant de fonctionnement du microprocesseur. De manière générale, lorsque le microprocesseur va chercher une instruction en mémoire, PC est sélectionné, et lorsque le microprocesseur va traiter une donnée en mémoire, AD est sélectionné. Le choix est contrôlé par le signal SEL_ADR.
Nous représenterons ce choix par l'équation suivante (ou le symbole "=" signifie une opération combinatoire) : ADR[7:0] = SEL_ADR * AD[7:0] + !SEL_ADR * PC[7:0]
4. Séquencement des opérations
Le signal NRESET permet de réinitialiser le fonctionnement du microprocesseur. Au premier cycle d'horloge suivant le passage à 1 du signal NRESET, le microprocesseur va chercher la première instruction du programme à exécuter qui se trouve implicitement à l'adresse "0" de la mémoire.
L'exécution d'une instruction s'effectue systématiquement en trois cycles d'horloge.
Le graphe de la machine à état est rappelé ici :

Graphe d'état de CTR
4.1 Premier cycle "IF"
Pendant le premier cycle (IF pour "instruction fetch") deux opérations sont réalisées simultanément :
- Le registre d'instruction I est chargé (LOAD_I = 1) avec le premier octet de l'instruction.
Ce premier octet est lu dans la mémoire à l'adresse donnée par le pointeur PC.
A cet effet, PC est positionné sur le bus d'adresse ADR de la mémoire (SEL_ADR = 0).
- La deuxième opération consiste à incrémenter le compteur de programme PC (INC_PC = 1).
Cette opération permettra, pour le cycle suivant, de disposer de l'adresse du deuxième octet de l'instruction.
4.2 Deuxième cycle "AF"
Pendant le deuxième cycle (AF pour "address fetch") deux opérations sont réalisées simultanément.
- Le registre d'adresse Addr est chargé (LOAD_AD = 1) avec le deuxième octet de l'instruction.
Ce deuxième octet est lu dans la mémoire à l'adresse donnée par le pointeur PC.
A cet effet, PC est positionné sur le bus d'adresse ADR de la mémoire (SEL_ADR = 0).
- La deuxième opération consiste à mettre à jour le compteur programme.
Deux cas peuvent se présenter :
- L'instruction n'est pas un saut. Il faut alors incrémenter le compteur de programme PC (INC_PC=1) de manière à disposer du pointeur du premier octet de l'instruction suivante.
- L'instruction est un saut (instruction JMP, instruction JNZ avec Z=0, ou encore, instruction JNC avec C=0). Dans ce cas, le pointeur programme PC reçoit la valeur de la donnée en mémoire à l'adresse PC (LOAD_PC = 1).
Remarque : pour simplifier, INC_PC est systématiquement mis à 1, En cas de saut, le bloc PC considérera le signal LOAD_PC comme étant prioritaire.
4.3 Troisième cycle "EX"
Le troisième cycle correspond à l'exécution proprement dite de l'instruction. L'opération réalisée dépend de l'instruction stockée dans le registre d'instruction I.
Dans tous les cas, le contenu de la mémoire à l'adresse déterminée par le pointeur stocké dans AD au cycle précédent est lu de la mémoire et placé sur le bus d'entrée D (SEL_ADR = 1).
-
Dans le cas d'une instruction "non opératoire" (NOP, STA, OUT, JMP, JNC ou JNZ), le contenu de l'accumulateur n'est pas modifié (LOAD_AZC = 0).
Dans le cas contraire, le résultat de l'opération est mémorisé dans l'accumulateur (LOAD_AZC = 1).
L'instruction LDA est considérée comme une instruction opératoire (A <= D, donc LOAD_AZC = 1).
-
Dans le cas de l'instruction OUT, la valeur du bit de poids faible de D, D[0], est stockée dans le registre BZ (LOAD_BZ = 1).
-
Dans le cas de l'instruction STA, le contenu de l'accumulateur A est stocké en mémoire (WRITE=1) à l'adresse mémoire déterminée par le pointeur stocké dans AD au cycle précédent.
Le chronogramme suivant permet de résumer les différents cas.

5. Travail demandé
Vous venez de lire les spécifications d'un petit microprocesseur, appelé nanoprocesseur, destiné à synthétiser de douces mélodies. Comme tout projet bien spécifié, différentes équipes ont pu travailler de façon indépendante à la conception des différents modules. Ainsi, la carte contenant les différents composants électroniques et acoustiques est prête, un programme assembleur de test et un autre générant de doux sons sont déjà écrits, le schéma global est terminé ainsi que les sous-blocs ALU, REG, MEMOIRE (RAM). Hélas, lors de la phase final d'intégration, le chef de projet réalise, tardivement, il est vrai, que les équipes chargées des blocs PC et CTR ont fait un travail de tellement piètre qualité, qu'il juge préférable de les re-concevoir complètement.
A vous de jouer...
6. Quelques indications et quelques conseils
Vous devez d'abord charger le fichier nano.gdf contenant l'assemblage au plus haut niveau hiérarchique du microprocesseur (bloc NANOPROC), de sa mémoire RAM (bloc MEMOIRE), et de deux blocs dont on ne s'occupe pas (le bloc CLOCK génère l'horloge du système et un signal de contrôle pour MEMOIRE ; le bloc TRANS permet uniquement de visualiser les mnémonique des commandes dans les chronogrammes afin de faciliter l'interprétation des résultats de simulation).
A partir du plus haut niveau de description hiérarchique, vous pouvez visualiser le contenu du bloc NANOPROC en cliquant 2 fois dessus. A partir du bloc NANOPROC, vous accédez de la même aux blocs PC ou CTR.
6.1 Conception du bloc PC

La première étape consiste bien évidemment à déterminer le fonctionnement exact de ce bloc à partir des spécifications globales. A partir de ces spécifications, il faut définir le schéma logique et enfin, le saisir.
Pour accélérer la saisie du bloc PC, outre les entrées/sorties, un incrémenteur, un multiplexeur et un registre paramétrable sont déjà instanciés (référez vous au manuel pour le paramétrage des blocs génériques).
Après avoir effectué une compilation en mode fonctionnel, il faudra créer un environnement de simulation afin de vérifier le bon fonctionnement du bloc PC.
Remarque : vous pouvez vous reporter à la leçon L7 sur le processeur pour vous aider...
6.2 Conception du bloc CTR

De nouveau, il faut commencer par extraire des spécifications le comportement exact de la machine à états, en déduire un schéma logique et en effectuer la saisie.
La solution consistant à concevoir ce bloc comme une machine de Mealy est plus simple que celle utilisant une machine de Moore.
Remarques :
-
un codage pour lequel chaque état est associé à un registre peut réduire la complexité de la logique combinatoire. Choisissez donc de préférence un codage "one hot".
-
Vous pouvez vous reporter à la leçon L7 sur le processeur pour vous aider...
6.3 Validation
Vous pouvez vérifier le bon fonctionnement en simulant directement le système complet (nanoprocesseur + mémoire) avec un programme de test. Ce programme, nommé verif_globale permet d'exécuter toutes les instructions du nanoprocesseur.
Pour effectuer ce test, il faut charger le programme dans la mémoire en affectant au paramètre générique MUSIQUE du bloc MEMOIRE (niveau le plus haut du projet) le nom du programme (c-a-d "verif_globale"). Après la compilation en mode fonctionnel du projet, vous pouvez lancer la simulation "nano.scf" et vérifier, sur les chronogrammes, le bon fonctionnement des blocs CTR et PC .
Une fois les blocs validés, en avant la musique...
Il faut d'abord charger le programme musical (paramètre MUSIQUE = "chicken.mif" du bloc MEMOIRE "nano.gdf"), ensuite effectuer une compilation en mode temporel, et enfin, faire un essai sur la carte de test avec un FPGA FLEX10K100ARC240-2 et un oscillateur à 20 MHz (minimum).
6.4 Question subsidiaires
- Certaines opérations peuvent s’exécuter en moins de cycles. Lesquelles, en combien de cycles ? Modifier le processeur de façon à optimiser son temps de fonctionnement.
- Partant du principe que certaines opérations n’ont pas besoin d’opérande (NOP, ROT, ROR), pourquoi ne pas réduire la taille du code en RAM ?
- On veut non seulement augmenter le nombre de sorties, disons à 16, mais aussi à pouvoir utiliser certaines d’entre elles non pas comme des sorties mais comme des entrées. Et ce, de façon dynamique : au cours du programme, un broche peut devenir un sortie, puis une entrée, puis une sortie etc. Comment l’implémenter ?
- Comment modifier le processeur pour supporter une taille mémoire de 161024 mots (10 bits) ?
|