wiki:SoclibCourseTp3

Version 25 (modified by alain, 15 years ago) (diff)

--

TP3 : Processeurs programmables

1 Objectif

L'objectif de ce troisième TP est d'introduire des processeurs programmables dans les architectures modélisées, puisque, pour des raisons de flexibilité et de re-utilisation des plate-formes matérielles, les concepteurs de systèmes intégrés essaient de réaliser le plus grand nombre possible de fonctions en logiciel, sur des processeurs généralistes, ou sur des processeurs de traitement du signal.

On utilisera des processeurs RISC 32 bits, car ce type de processeur possède un très bon rapport (puissance de calcul) / (consommation énergétique).

On introduira également dans l'architecture les mémoires embarquées contenant le code binaire et les données de l'application logicielle.

2 Architecture matérielle cible

L'architecture matérielle modélisée dans ce TP comporte un seul initiateur VCI et 4 cibles VCI :

  • xcache est un processeur MIPS32 avec ses caches L1. On utilise le composant VciXcacheWrapper, qui est un contrôleur de cache à interface VCI. Ce composant générique encapsule un autre composant !Mips32Iss, qui modélise un coeur de processeur MIPS32.
  • rom est une mémoire non inscriptible à interface VCI contenant le code de boot. On utilise le composant VciSimpleRam.
  • ram est une mémoire inscriptible à interface VCI contenant le code et les données. On utilise également un composant VciSimpleRam.
  • tty est un périphérique adressable de type écran/clavier à interface VCI. On utilise le composant VciMultiTty.
  • gcd est le coprocesseur cible réalisant le calcul du PGCD. On utilise évidemment le composant VciGcdCoprocessor.
  • vgsb est le bus système déjà utilisé dans le TP2. On utilise le composant VciVgsb.

Les modèles de simulation des composants matériels instanciés dans cette architecture sont disponibles dans la bibliothèque SoCLib. Ils vous sont fournis, et vous n'aurez pas à les re-écrire vous-même. Vous pouvez obtenir une description fonctionnelle détaillée pour chacun de ces composants sur le site WEB de !oCLib : https://www.soclib.fr/trac/dev/wiki/Component

Le composant VciXcacheWrapper peut être utilisé pour encapsuler différents processeur 32 bits. Le coeur du processeur est modélisé par un ISS (Instruction Set Simulateur). Le type du proceseur instancié (MIP32, ARM, SPARCV8, PPC405, NIOS, MicroBlaze, etc.) est défini par un paramètre template du composant VciXcacheWrapper. Consultez la documentation ici.

Le composant VciSimpleRam est utilisé pour modéliser des mémoires inscriptibles embarquées (SRAM), ou pour modéliser des mémoires non inscriptibles (ROM). Ce composants peut contenir un ou plusieurs segments (correspondant à des tranches disjointes de l'espace addressable). Le composant analyse les bits de poids fort de l'adresse VCI pour déterminer le segment désigné. Les bancs de mémoire physique correspondant aux différents segments sont modélisés par des tableaux C++ dont la longueur est définie par les valeurs stockées dans la MappingTable. Consultez la documentation ici.

Enfin le composant VciMultiTty est un contrôleur de terminaux alpha-numériques. Ce composant peut contrôler de 1 à 256 terminaux (un terminal est une paire écran / clavier). Pratiquement, chaque terminal est modélisé par l'ouverture d'une fenêtre XTERM indépendante sur l'écran de la station de travail qui exécute la simulation. Chaque terminal possède 4 registres adressables pour la lecture ou l'écriture, et fonctionne en mode caractère: on ne peut lire ou écrire qu'un seul caractère par transaction. Consultez la documentation ici.

3 Génération et chargement du logiciel embarqué

Il existe plusieurs façons de définir et de générer le code binaire qui sera exécuté par le (ou les) processeur(s) du MPSoC. Si on part d'une application logicielle écrite en langage C, il faut utiliser un cross-compilateur spécifique pour le processeur choisi. Le résultat est un fichier binaire au format ELF. Le code binaire correspondant doit ensuite être chargé dans les mémoires embarquées du MPSoC. Il y a donc deux étapes bien distinctes pour la génération et le chargement.

3.1 Génération du code

Les composants de la bibliothèque SoCLib permettent de modéliser des architectures matérielles complexes, capables d'exécuter des systèmes d'exploitation avancés (tels que LINUX, Unix NetBSD, ou MUTEK). Mais dans ce TP et les suivants, on se contentera d'un système d'exploitation minimal, constitué par un Gestionnaire d'Interruptions, Exceptions et Trappes (GIET), quelques appels systèmes permettant aux programmes utilisateurs d'accéder aux périphériques, plus le code boot pour initialiser la machine. Tout ce code système est écrit en C et en assembleur MIPS32, et il vous est fourni. Les programmes utilisateurs seront écrits en langage C, et seront écrits par vous.

Les processeurs disponibles dans SoCLib, sont en majorités des processeurs RISC, capables de démarrer une instruction à chaque cycle, grâce à leur structure pipe-line. Le processeur doit, à chaque instruction lire l'instruction dans le cache, la décoder et l'exécuter. C'est évidemment le contrôleur du cache qui se charge d'aller lire le code binaire chargé dans les mémoires embarquées en cas de MISS.

Le code binaire doit donc être généré par un cross-compilateur spécifique pour le processeur MIPS32. On utilise la chaîne de compilation GCC, pour compiler l'ensemble du logiciel embarqué (code applicatif et code système) et pour réaliser l'édition de liens entre le code applicatif écrit par vous, et le code système. Le résultat de cette compilation est un fichier au format ELF, contenant le code binaire exécutable par le processeur MIP32.

3.2 Chargement du code

Il existe deux méthode méthodes permettant de charger le code binaire dans les mémoires embarquées sur la puce:

  1. Le code peut être stocké dans des mémoires mortes (ROM). Le contenu de ces mémoires est défini lors de la fabrication de la puce, et n'est plus modifiable. Cette approche est évidemment très peu flexible, et elle n'est généralement utilisée que pour le code de boot.
  2. Le code peut être stocké dans des mémoires inscriptibles (SRAM), qui sont chargées lors de la mise sous tension du système à partir d'un périphérique de stockage externe (cela peut être une ROM externe, une mémoire flash, ou un autre dispositif de stockage. On peut même imaginer qu'on utilise une liaison sans fil pour télécharger du code applicatif disponible sur un serveur distant. Cette approche est utilisée pour le code applicatif, mais également pour le système d'exploitation embarqué. Le code qui exécute ce chargement de code s'appelle un bootloader.

La phase de chargement du système d'exploitation et du code applicatif est en pratique exécutée à chaque mise sous tension, ou chaque fois qu'on active le signal NRESET. Elle peut être très longue (plusieurs millions de cycles). Un fois que le bootloader a été validé, cette phase d'initialisation n'apporte plus beaucoup d'information, quand on souhaite mettre au point ou mesurer les performances d'une application logicielle sur une architecture matérielle modélisée avec SoCLib.

La plate-forme SoCLib fournit donc un service permettant d'initialiser directement les mémoires embarquées à partir du code contenu dans le fichier ELF. Cette initialisation n'est plus réalisée lors de l'exécution de la simulation (dans la phase de boot), elle est réalisée avant le démarrage de la simulation par le constructeur des composants modélisant des mémoires embarquées (ROM ou RAM). Le constructeur du composant VciSimpleRam possède un argument loader qui lui permet d'accéder au contenu du fichier ELF contenant le code binaire. Le constructeur possédant un autre argument lui permettant d'accéder à la MappingTable?, il peut déterminer quels segments de la RAM (ou de la ROM) doivent être initialisés.

On économise ainsi plusieurs millions de cycles de simulation, et le code de boot peut être beaucoup plus court.

4 Travail à réaliser

L'archive soclib_tp3.tgz contient différents fichiers dont vous aurez besoin pour ce TP. Créez un répertoire de travail spécifique TP3, recopiez l'archive dans ce répertoire TP3, et décompressez-la:

$ tar xzvf soclib_tp3.tgz

Cette archive contient un très grand nombre de fichiers, car les composants instanc!és sont des objets complexes, qui font appel à beaucoup de code caché : Par exemple, le chargement initial des mémoires embarquées ROM et RAM nécessite l'analyse du format de fichier binaire ELF. De même, l'utilisation d'un contrôleur de terminal suppose l'ouverture d'une ou plusieurs fenêtres XTERM sur la station de travail qui exécute la simulation.

Vous pouvez consulter le fichier Makefile pour avoir la liste de tous les fichiers objets qui doivent être compilés pour générer le simulateur de cette architecture très simple.

Elle contient également un sous-répertoire soft qui est utilisé pour la génération du logiciel embarqué.

4.1 Segmentation de l'espace adressable

Cette architecture nécessite la définition de 7 segments:

  • seg_tty est le segment associé au contrôleur de terminaux TTY. On prendra pour adresse de base la valeur 0xC0000000, et pour longueur 64 octets, ce qui permet d'adresser jusqu'à 4 terminaux indépendants.
  • seg_gcd est le segment associé au coprocesseur GCD. On prendra pour adresse de base la valeur 0x90000000. La longueur de 16 octets correspond aux quatre registres adressables de ce composant.
  • seg_reset est le segment contenant contient le code de boot exécuté à la mise sous tension. Il est évidemment assigné à la ROM. L'adresse de base 0xBFC00000 est imposée par la spécification du processeur MIPS32. On choisira une capacité de stockage de 4Koctets.
  • seg_kernel est le segment contenant le code du système qui s'exécute en mode kernel. Il s'agit principalement du Gestionnaire d'Interruptions, Exceptions, et Trappes (GIET) et du code des appels systèmes. Ce segment est assigné à la RAM. L'adresse de base 0x80000000 est imposée par la spécification du processeur MIPS32 qui impose que le point d'entrée est à l'adresse 0x80000180. On choisira une capacité de stockage de 4 Koctets.
  • seg_text est le segment contenant le code de l'application logicielle embarquée, qui s'exécute en mode user. Il est assigné à la RAM. On choisira pour adresse de base la valeur 0x00400000, et une capacité de stockage de 16 Koctets.
  • seg_data est le segment contenant les données globales et la pile d'exécution de l'application logicielle embarquée. Il est assigné à la RAM. On choisira pour adresse de base la valeur 0x00000000, et une capacité de stockage de 64 Koctets.
  • seg_stack est le segment contenant la pile d'exécution de l'application logicielle embarquée. Il est assigné à la RAM. On choisira pour adresse de base la valeur 0x00800000, et une capacité de stockage de 64 Koctets.

Remarquez que les 2 segments correspondant aux périphériques (seg_tty et seg_lcd), ainsi que les deux segments correspondant au code système sont dans la zone protégée de l'espace adressable, qui n'est accessible qu'en mode kernel (adresses supérieures à 0x80000000).

Remarque importante : Certaines informations sont utilisées à la fois par le matériel et par le logiciel embarqué, et doivent donc être définies à deux endroits :

  1. Les addresses de base et les longueurs des segments sont utilisées par le matériel : Elles doivent être définies dans le fichier tp3_top.cpp pour ëtre stockées dans la MappingTable, qui est est utilisée dans la phase de configuration du matériel par les constructeurs des composants. Ces mêmes adresses de base des segments sont utilisées par le logiciel et doivent être définies dans le fichier soft/ldscript qui contient les directives pour l'éditeur de liens lors de la compilation du logiciel embarqué.
  2. Le composant matériel générique VciMultiTty peut contrôler un nombre variable de terminaux. Ce nombre de terminaux doit être défini dans le fichier tp3_top.cpp (pour le matériel), mais doit aussi être défini dans le fichier soft/ldscript, pour informer le système d'exploitation du nombre de terminaux adressables.

Complêtez les fichiers tp3_top.cpp et soft/ldsript pour définir les adresses de base et les longueurs des segments, ainsi que le nombre de terminaux utilisés.

4.2 Compilation du logiciel embarqué

Le logiciel embarqué est défini dans plusieurs fichiers source, que vous trouverez dans le répertoire soft. Certains de ces fichiers sont écrits en assembleur MIPS32, certains sont écrits en C :

  • le fichier reset.s est écrit en assembleur et contient le code de boot qui est exécuté à la mise sous tension, ou lors de l'activation du signal NRESET. Ce code s'exécute en mode kernel et initialise quelques registres, avant de se brancher à la pemière instruction du programme main.
  • le fichier giet.s est écrit en assembleur et contient le code du Gestionnaire d'Interruption, Exceptions et Trappes. Le GIET est l'unique point d'entrée dans le système d'exploitation. Ce code s'exécute en mode kernel, et se termine toujours par une instruction eret.
  • le fichier stdio.c est écrit en C, et contient le code des fonctions C permettant à un programme applicatif s'exécutant en mode user d'accéder aux périphériques mappés dans le segment kernel, en effectuant des appels système.
  • le fichier syscalls.s est écrit en C et contient le code des appels systèmes proprement ditS. Ce code s'exécute en mode kernel, et permet l'accès aux périphériques ou aux registres protégés du processeur.
  • le fichier main.c est écrit en C et contient le code de l'application logicielle, qui peut évidemment utiliser

les fonctions définies dans le fichier stdio.c.

  • le fichier ldscript contient les directives pour l'éditeur de liens.
  • le fichier Makefile permet de lancer la génération du logiciel embarqué.

Question : Quels sont les appels système qui permettent d'accéder à un terminal TTY ? Lorsqu'il y a plusieurs terminaux dans l'architecture, comment est sélectionné le terminal cible ?

On rappelle que l'instruction eret de sortie du GIET ou du code de boot effectue principalemnt deux actions :

  1. Elle modifie le registre protégé SR (registre 12 du coprocesseur système) pour que le processeur retourne dans le mode où il était lorsqu'il a été dérouté par une interruption, une exception ou un appel système.
  2. Elle effectue un branchement à l'adresse contenue dans le registre protégé EPC (registre 14 du coprocesseur système).

Question : Quels sont les initialisations réalisées par le code de boot ? pouquoi ces initialisations ?

Le premier programme que vous allez exécuter se contente d'afficher le célèbre hello world sur le terminal. Ouvrez le fichier soft/main.c.

Question : Que fait ce programme ? (on rappelle que la fonction tty_getc() est bloquante, et ne rend pas la main tant qu'un caractère n'a pas été saisi au clavier).

Lancez l'exécution du Makefile. Deux fichiers soft.bin et soft.bin.txt doivent être créé dans le répertoire soft : Le fichier soft.bin contient le code binaire au format ELF, et le fichier soft.bin.txt contient un version desassemblée (donc lisible) de ce code binaire.

4.3 Définition de l'architecture matérielle

Complétez le fichier tp3_top.cpp: Il faut enregister les 7 segments dans la MappingTable?. Il faut définir les arguments de tous les constructeurs des composants instanciés, ainsi que les valeurs de leurs paramètres template. Il faut définir le cheminom permettant au loader des composants ROM et RAM d'accéder au fichier soft/bin.soft contenant le code binaire.

Question : Parmi les 7 segments utilisés dans cette l'architecture, lesquels doivent être déclarés cachables ?

Question : Quel est le nombre de bits de poids fort de l'adresse qui doivent être décodés par le contrôleur du bus pour déterminer la cible VCI désignée ? Cette information est un des arguments du constructeur de la MappingTable.

Question quels sont les bits d'adresse qui doivent être décodés par le contrôleur du cache, pour déterminer qu'une adresse appartient à un segment non-cachable, et doit être directement transmise à la mémoire ? Cette information est un autre argument du constructeur de la MappingTable.

Enfin, comme dans le TP2, il faut modifier tous les fichiers des composants SoCLib qui possèdent des paramètres templates pour définir les valeurs de ces paramètres avant de générer le fichier objet correspondant. Dans le cas du coprocesseur GCD, il faut modifier le fichier vci_gcd_coprocessor.cpp en ajoutant :

template class VciLcdCoprocessor<soclib::caba::VciParams<4, 8, 32, 1, 1, 1, 12, 1, 1, 1> >;

Il faut faire de même pour les autres composants matériels instanciés.

Quand tout ceci est fait, lancez le Makefile qui vous est fourni dans le répertoire TP3, pour générer le simulateur simulator.x.

4.5 Lancement de la simulation

Lancez la simulation en lançant la commande :

$ ./simulator.x 2000

En cas de problème lors de l'éxécution, vous pouvez relancer la compilation du simulateur en activant le mode DEBUG. Il faut modifier le fichier Makefile en ajoutant un flag supplémentaire dans la list des flags de gcc :

CFLAGS = -Wno-deprecated -DSOCLIB_MODULE_DEBUG

Ce flag permet d'activer les directives de compilation conditionnelle qui se trouvent à la fois dans le fichier tp3_top.cpp, et dans les modèles des composants matériels.

Pour attirer votre attention sur des erreurs fréquentes, faites les essais suivants :

  1. Modifiez l'adresse de base du segment seg_lcd pour lui donner la valeur 0xB0000000 au lieu de 0x9000000. Relancez la compilation et la simulation. Expliquez les résultats obtenus.
  1. Déclarez les segments correspondant aux périphériques (seg_tty et seg_lcd) comme cachables. Relancez la compilation et la simulation. Expliquez les résultats obtenus.

4.5 Modification du logiciel embarqué

Puisque le logiciel embarqué est chargé dynamiquement dans la RAM et dans la ROM lors du lancement du simulateur, il est possible de modifier le logiciel embarqué (fichier bin.soft), sans modifier l'architecture matérielle et donc sans recompiler le simulateur et sans avoir à regénérer le fichier simulator.x.

On va donc maintenant écrire une application logicielle un peu plus complexe, qui utilise le coprocesseur LCD, simplement en modifiant le fichier main.c dans le répertoire soft, et en relançant la compilation et la génération du fichier bin.soft.

Modifiez le fichier main.c, pour que les programme C exécute une boucle infinie dans laquelle on effectue successivement les sept opérations suivantes :

  1. affichage du numéro de cycle et du numéro d'itération.
  2. génération aléatoire de deux variables OPA et OPB de type int.
  3. écriture de OPA dans le registre r_opa du coprocesseur LCD.
  4. écriture de OPB dans le registre r_opb du coprocesseur LCD.
  5. écriture dans le pseudo-registre r_start du coprocesseur LCD, pour démarrer la simulation.
  6. lecture dans le registre r_res du coprocesseur LCD pour récupérer le résultat.
  7. affichage des valeurs des opérandes et du résultat sur le TTY.

Pour afficher sur le terminal, on utilisera évidemment la fonction printf(). Pour obtenir le numéro de cycle, on utilisera la fonction... Pour la génération aléatoire, on utilisera la fonction rand(). Pour les accès au coprocesseur LCD on utilisera les fonctions...

Le code de ces fonctions est défini dans le fichier...

5 Compte-rendu

Il ne vous est pas demandé de compte-rendu pour ce TP, mais on vous demandera une démonstration de votre simulateur au début du TP de la semaine suivante...

Attachments (2)

Download all attachments as: .zip