3MMCEP Projet Intro

De Ensiwiki
Aller à : navigation, rechercher
AttentionCette page est maintenue uniquement par les enseignants. Afin de ne pas perturber le déroulement des cours, elle n'a pas vocation à être modifiée par les élèves. Mais si vous avez des modifications à proposer, merci d'en discuter ou d'envoyer un e-mail aux auteurs de la page (cf. historique)
Laptop.png  Première Année  CDROM.png  Informatique 
Fleche haut.png

On décrit de façon détaillée le travail à réaliser pour la séance 2 de projet : par la suite, vous devrez vous organiser vous-même pour réaliser les différentes étapes détaillées dans le cahier des charges selon le planning imposé.

Structure des sources fournies

On va utiliser pour ce projet le gestionnaire de versions Git que l'on vous a présenté pendant la semaine d'introduction au C. Vous devrez utiliser Git pour récupérer les sources de départ ainsi que pour valider votre réalisation au fur et à mesure que vous implanterez des étapes. Vous pouvez aussi vous en servir pour effectuer des sauvegardes, vu que votre dépôt Git sera localisé physiquement sur le serveur telesun.

Attentiontelesun n'existe plus !


Pour récupérer les sources initiales, vous devez d'abord exécuter la commande git clone LOGIN1@telesun.imag.fr:/home/users/depotsCEP/cep_LOGIN1_LOGIN2 où LOGIN1 et LOGIN2 sont les logins des membres du binôme par ordre alphabétique (vous pouvez aussi utiliser le LOGIN2 pour vous connecter sur telesun : les deux membres du binômes peuvent accéder au dépôt). Cette commande crée un sous-répertoire cep_LOGIN1_LOGIN2 dans lequel vous trouverez les sources de départ.

On détaille ci-dessous celles concernant spécifiquement la structure du processeur à réaliser.

La Partie Contrôle (PC)

La partie contrôle du processeur est décrite dans le fichier MMIPS_CPU_PC.vhd : c'est celui que vous aurez le plus à modifier pendant le projet. Il est commenté de façon à vous aider à le comprendre.

En résumé, vous y trouverez :

  • la description des ports d'entrée et de sortie de la PC : vous faciliter la lisibilité, on a choisi de nommer toutes les commandes de la PC vers la PO en commençant par cmd_ et tous les retours de la PO vers la PC en commençant par feedback_ : on vous recommande de garder un système de nommage cohérent dans votre propre code ;
  • la liste des états de l'automate de contrôle (défini sous la forme d'un type énuméré : type State_type is (S_Init,S_fetch_wait,S_fetch);) : vous devrez bien sûr completer cette liste au fur et à mesure que vous rajouterez des états ;
  • un processus FSM_synchrone servant à faire passer l'automate de l'état courant à l'état suivant (c'est en fait le comportement du registre d'état)  : vous ne devez pas modifier ce processus ;
  • un processus FSM_comb qui contient la description fonctionnelle de l'automate de la PC : c'est ce processus, qui contient à la fois la fonction de transition et la fonction de génération, qu'il faut compléter.

Le code du processus FSM_comb commence par une suite de « commandes par défaut » : il s'agit en fait d'une façon simple de factoriser le code de production des sorties. En effet, chaque sortie doit avoir une valeur définie pour chaque état de l'automate : cela veut dire que si par exemple, la sortie S vaut 1 dans l'état X et 0 dans les 20 autres états de l'automate, vous devez écrire S <= 0 dans les 20 états en question. Pour éviter cela, il est plus simple d'affecter des valeurs par défaut à chacune des sorties, et de ne préciser dans les différents état de l'automate que les sorties dont la valeur diffère de la valeur par défaut : c'est bien la dernière valeur qui sera utilisée pour chaque sortie.

Ensuite, vous trouverez un case basé sur l'état courant : il s'agit de l'implantation de l'automate de Moore de la PC. Pour chaque état de la liste des états définie plus haut, vous devez donc :

  • produire une valeur pour chacune des sorties dont la valeur diffère de celle par défaut ;
  • calculer l'état suivant, en affectant etat_futur.

Attention, on rappelle que dans un automate de Moore, les sorties ne dépendent que de l'état courant, et pas des entrées (sinon, cela serait un automate de Mealy) : vous ne pouvez donc pas écrire dans votre code des constructions du type :

when S_XYZ =>
    if feedback_c = '1' then
        cmd_def <= "101";
    else
        cmd_def <= "111";
    end if;
    ...

sauf pour l'entrée feedback_ir qui est directement issue de la sortie du registre IR dans la PO, et est donc stable durant toute la durée du cycle.

La Partie Opérative (PO)

On décrit dans le fichier MMIPS_CPU_PO.vhd la partie opérative du processus. On vous fourni une PO de départ qui vous permettra de réaliser les premières étapes sans avoir besoin de modifier cette PO. Par contre, vous devez lire son code pour comprendre comment elle est écrite et faire le lien avec le schéma ci-dessous (note : les parties en jaunatre ne sont pas implantées dans la PO fournie : elles devront être ajoutée pendant la séance 6) :

CEP Projet PO S2.png

Les conventions de codage utilisées dans la PO fournie sont détaillées dans le cahier des charges.

Le processeur

Le fichier MMIPS_CPU.vhd contient le processeur, c'est à dire la connexion de la PC et de la PO. Vous ne devez pas à modifier l'interface de ce fichier pendant le projet car le système de validation automatique se base sur cette interface. Vous pouvez par contre y rajouter des signaux internes si vous modifiez les interfaces de la PC et de la PO (cela sera notamment nécessaire pour implanter la gestion des interruptions).

Le système complet

Un processeur isolé ayant peu d'intérêt, on va le relier à de la mémoire et des périphériques qui vont permettre notamment de communiquer avec lui. Le fichier MMIPS_et_environnement.vhd décrit l'interconnexion du processeur avec son environnement. Vous aurez peut-être à modifier ce fichier vers la fin du projet, si vous ajoutez des extensions. La figure ci-dessous détaille la structure globale du système (les parties en jaunâtre seront à rajouter si vous développez des extensions) :

CEP Projet Systeme Complet.png

Le générateur de fréquence

Le fichier clock_gen.vhd contient un circuit chargé de générer une fréquence acceptable par le processeur. En pratique, il s'agit d'un simple diviseur de fréquence, qui prend en entrée l'horloge 50 MHz de la carte FPGA et produit en sortie une fréquence de 25 MHz.

Vous trouverez dans le sous-répertoire PourImplem une version du même composant que l'on vous recommande d'utiliser lorsque vous testerez votre processeur sur la carte FPGA (les deux versions sont fonctionnellement équivalentes, mais la version simpliste fournie par défaut dans les sources de base peut causer des problèmes lors de l'implantation). Le Makefile fourni doit utiliser automatiquement la bonne version selon si vous programmez la carte ou testez en simulation, donc vous n'avez en pratique rien à faire.

La mémoire programme

Un processeur a pour fonction d'exécuter des instructions composant un programme : il faut donc lui fournir ces instructions. On les stocke ici dans une mémoire que l'on va appeler « mémoire programme » décrite dans le fichier RAM_PROG.vhd.

Dans ce fichier, la partie intéressante est l'initialisation du tableau zone_memoire :

    0 => x"00000000", --    0:	00000000 	nop
    1 => x"00000000", --    4:	00000000 	nop
    2 => x"00000000", --    8:	00000000 	nop
    3 => x"00000000", --    c:	00000000 	nop
    others => x"FFFFFFFF" -- reset
    );

Chaque case du tableau zone_memoire correspond à un mot de 32 bits contenant une instruction. Les instructions sont ici codées en hexadécimal (par exemple : x"00000000") et on donne en commentaire la représentation symbolique de l'instruction (e.g. nop).

Vous pouvez donc écrire vos programmes en hexadécimal dans le code d'initialisation du tableau zone_memoire... ce qui devient vite très fastidieux pour un programme un peu long !

Pour vous faciliter la vie, on vous fournit un script asm2mem capable de traduire un programme écrit en assembleur MIPS dans la suite d'instruction VHDL permettant d'initialiser la mémoire.

Par exemple, le fichier test_nop.s contient le même programme que celui décrit dans la mémoire par défaut. Si vous exécutez dans un terminal la commande ./asm2mem test_nop.s, le script génère automatiquement un nouveau fichier RAM_PROG.vhd contenant le programme donné en assembleur (ici, le même fichier donc). Attention : le script ne demande pas confirmation avant d'écraser le fichier RAM_PROG.vhd, veillez donc à le sauvegarder si vous en avez besoin.

Vous pouvez aussi regarder les fichiers test_lui.s et test_fpga.s fournis qui contiennent des programmes un peu plus intéressants que celui par défaut.

Le périphérique de mise au point

On vous fournit enfin un fichier IP_DEBUG.vhd pour vous aider à mettre au point vos programmes. Le composant décrit dans ce fichier permet d'afficher la valeur du registre 28 sur les LEDS de la carte FPGA (le registre 28 sera par défaut le registre de « sortie » de notre processeur, c'est à dire celui dans lequel on écrira des « traces »). Comme on ne dispose que de 8 LEDS sur la carte FPGA, le composant affiche un des 4 octets composant le mot de 32 bits stocké dans le registre 28, en fonction de la valeur des boutons 0 et 1 :

Utilisation du composant IP_DEBUG
Bouton 1 Bouton 0 Octet affiché
relaché relaché 7..0
relaché appuyé 15..8
appuyé relaché 23..16
appuyé appuyé 31..24

Travail à réaliser pendant la séance 2

Si vous suivez le planning détaillé dans le cahier des charges, vous devez implanter dans votre processeur la gestion de trois instructions aujourd'hui :

  • nop ("No-Operation") : cette pseudo-instruction ne fait rien, et prend un cycle pour le faire : elle correspond au codage 0x00000000, ce qui veut dire qu'elle est équivalente à l'instruction sll $0, $0, 0 que vous implanterez plus tard : l'intérêt de cette instruction est de vous permettre de tester que votre automate réaliser correctement la succession d'états S_fetch, S_decode, S_nop, S_fetch, ... ;
  • lui (Load Upper Immediate) : cette instruction charge dans les 16 bits de poids forts du registre précisé en argument 1 la constante sur 16 bits donnée en argument 2 et met à zéro les 16 bits de poids faibles du registre : par exemple, lui $28, 0xAA55 charge dans le registre 28 la constante 0xAA550000 ;
  • ori (OR Immediate) : cette instruction complète la constante 16 bits donnée en argument 3 avec des 0 dans les 16 bits de poids forts, et effectue une disjonction inclusive entre cette constante étendue et le registre précisé en argument 2 (rs), et stocke le résultat dans le registre précisé en argument 1 (rt) : par exemple, ori $28, $28, 0xFF force à 1 les 8 bits de poids faible du registre 28.

Pour cela, vous allez devoir modifier la PC du processeur pour implanter l'automate ci-dessous :

3MMCEP Projet PC S2.png

L'état suivant les états S_nop, S_lui et S_ori est S_fetch, par contre pour certaines instructions que vous gérerez dans les étapes suivantes, il faudra passer par l'état S_fetch_wait pour laisser un cycle de stabilisation...

On détaille ci-dessous le rôle de chaque état de l'automate :

  • S_init est l'état initial correspondant à une ré-initialisation du processeur : l'opération RTL (Register Transfer Level) associée est PC := 0 ;
  • S_fetch_wait est un état d'attente nécessaire à chaque fois que l'instruction précédente a provoqué un changement dans la valeur sortant sur le bus adresse (par ex. dans le cas d'un branchement ou d'une écriture mémoire) : aucune opération n'est réalisée dans cet état ;
  • S_fetch est l'état lisant l'instruction située en mémoire à l'adresse contenue dans PC et la recopiant dans le registre dédié IR : IR := MEM(PC) ;
  • S_decode est l'état de décodage de l'instruction courante, c'est à dire l'état dans lequel on analyse son code-opération (codop) pour déterminer de quelle instruction il s'agit ;
  • S_nop, S_lui et S_ori sont les états dans lesquels on implante la gestion des instructions nop, lui et ori comme décrit plus haut.

En pratique, vous devez compléter l'état S_fetch et ajouter les états S_decode, S_nop, S_lui et S_ori.

Il faut aussi penser à avancer PC pour qu'il pointe sur l'instruction suivante (ce qui consiste à ajouter 4 à PC vu que toutes les instructions font 32 bits) avant de revenir à l'état S_fetch : le meilleur endroit pour faire cela est dans l'état de décodage de l'instruction.

Pour décoder les instructions, il faut évidemment respecter le codage détaillé dans le cahier des charges. On rappelle ci-dessous le codage des trois instructions gérées aujourd'hui :

Codage de l'instruction nop
bits 31 à 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Codage de l'instruction lui
bits 31 à 26 25 ... 21 20 ... 16 15 ... 0
0 0 1 1 1 1 0 0 0 0 0 registre rt constante sur 16 bits
Codage de l'instruction ori
bits 31 à 26 25 ... 21 20 ... 16 15 ... 0
0 0 1 1 0 1 registre rs registre rt constante sur 16 bits

La pseudo-instruction nop est en fait équivalente à l'instruction sll $0, $0, 0 : vous pourrez fusionner l'état S_nop dans celui gérant les décalages à gauche quand vous les aurez implantés.

Les deux instructions lui et ori sont conformes au modèle d'instruction I du cahier des charges, ce qui signifie que :

  • les bits 31 à 26 constituent le codop de l'instruction
  • les bits 25 à 21 codent le registre rs (dans le cas de l'instruction lui ils sont forcés à 0 car cette instruction n'admet qu'un seul registre comme paramètre) ;
  • les bits 20 à 16 codent le registre rt ;
  • les bits 15 à 0 codent la constante sur 16 bits.

Mise au point du projet

Pour tester votre projet, vous allez devoir écrire des petits programmes de test en assembleur MIPS et valider que le processeur se comporte comme prévu en simulation lorsqu'il exécute ces programmes.

Aujourd'hui, vous devez donc écrire un programme testant l'instruction ori : vous pouvez vous inspirer par exemple du programme suggéré pour tester sur FPGA ci-dessous.

Attention : à la fin du projet, on vous demandera de rendre les tests que vous écrirez pour tester votre réalisation : vous ne devez donc pas vous contenter d'utiliser l'outil de validation en ligne pour mettre au point votre code, et vous devez écrire des fichiers de test pour chaque instruction ajoutée.

On rappelle que pour générer la mémoire programme correspondant à un programme assembleur MIPS, vous pouvez utiliser l'outil asm2mem. Pour lancer la simulation, utilisez ./regenerate.sh --simu.

Le script regenerate.sh nettoie complètement votre projet et le copie sur le disque local avant de lancer la simulation : cela garantit un meilleur fonctionnement des outils utilisés, mais au prix d'un temps de génération plus long. Pour aller plus vite, vous pouvez aussi taper make tb_MMIPS_et_environnement.view pour lancer des simulations incrémentales de votre projet.

Note : les utilitaires nécessaires au script asm2mem sont installés sur telesun dans le répertoire /usr/local/mips-elf-linux-cep, vous pouvez donc rajouter /usr/local/mips-elf-linux-cep/bin dans votre PATH si vous souhaitez simuler sur telesun : mais on vous recommande fortement de travailler en local sur les PC pour bénéficier de la puissance de calcul.

Mécanisme de validation

Pour vous aidez à mettre au point votre processeur, ainsi que pour nous permettre d'évaluer votre progression et votre travail, on fourni un mécanisme d'évaluation semi-automatique de votre code VHDL basé sur une base de tests standards.

Pour faire passer ces tests, vous devez déposer vos fichiers dans le dépôt Git sur telesun : pour cela, vous utiliserez les commandes suivantes :

  1. git commit -am "un commentaire décrivant l'etape a valider"
  2. git push

La commande commit enregistre vos modifications dans votre dépôt de travail local (i.e. sur le PC sur lequel vous travaillez) alors que la commande push envoie ces modifications sur telesun.

Attention : seuls les fichiers MMIPS_CPU.vhd, MMIPS_CPU_PC.vhd et MMIPS_CPU_PO.vhd sont analysés par l'outil de validation. Mais vous pouvez déposer tous vos fichiers dans le dépôt telesun tout de même : cela permettra d'en garder une sauvegarde. Comme précisé plus haut, l'interface du composant MMIPS_CPU ne doit pas être modifiée.

Une fois les fichiers déposés, vous pouvez accéder à l'outil de validation à l'adresse suivante : https://telesun.imag.fr:9001/ et vous connecter avec vos login et mot de passe habituels.

Note : cette adresse n'est accessible que depuis les machines raccordées physiquement au réseau de l'Ensimag. Si vous souhaitez y accéder depuis l'extérieur ou via le wifi, vous pouvez créer un tunnel SSH en tapant la commande suivante dans un terminal : ssh -N -L 9001:telesun.imag.fr:9001 telesun.imag.fr puis en accédant à l'adresse suivante : https://localhost:9001/.

Une fois connecté à l'application il vous suffit de cliquer sur le lien TESTS pour lancer les tests : attention, cela peut prendre quelques minutes. Ensuite, une page vous indiquera quels tests sont validés.

Programmation de la carte FPGA

Une fois que vous avez vérifié que votre implantation est correcte, vous pouvez la tester sur la carte. Pendant le déroulement du projet, il sera intéressant de tester périodiquement votre réalisation sur carte, en réalisant des petits programmes de tests.

Pour cette séance, vous pouvez par exemple utiliser le programme suivant (à vous de deviner ce qu'il est sensé faire) :

    .text
    lui $28, 0xAA55
    ori $28, $28, 0xFF00

En regardant le code généré dans RAM_PROG.vhd, vous observerez que l'assembleur MIPS ajoute parfois des instructions nop à la fin des programmes, pour respecter des contraintes d'alignement mémoire. Ces ajouts ne doivent bien sûr rien changer à la sémantique des programmes écrits.

Pour programmer la carte, vous pouvez utiliser la commande ./regenerate.sh --prog.

Cross - Compiler des programmes C

Il est possible de générer des programmes assembleurs directement à partir d'un code C à l'aide d'un cross-compilateur. A la différence d'un compilateur qui génère du code machine pour la machine sur laquelle il est exécuté, un cross-compilateur génère du code machine pour un autre processeur. L'archive Fichier:MIPS cross compile.tar.gz contient quelques utilitaires à cette fin. Ils permettent de générer RAM_PROG.vhd à partir de fichiers .c et .h. Son usage est détaillé dans le fichier README. Les exemples fournis restent des exemples et doivent être adaptés à votre projet pour fonctionner.

Attention: La canevas de gestion d'interruption ne gère pas la sauvegarde des registres au début du traitant d'interruption. Vous devez l'ajouter.

Le cross-compilateur est installé sur les machines ensiarchi dans /opt/mips-toolchain-bin/bin/ Vous pouvez installer sur votre machine personnelle le cross-compilateur "bare metal", dont Mips_toolchain-4.4.6-bin.tar.bz2 est une archive pré-compilée. Après décompression le cross-compilateur occupe 1,5 GO !