{{{ #!html

TP2: Protocole de communication VCI/OCP

}}} [[PageOutline]] = 1 Objectif = L'objectif de ce second TP est d'introduire la modélisation SystemC d'architectures utilisant le protocole de communication VCI/OCP. Pour des raisons d'inter-opérabilité, tous les composants matériels de la plate-forme de prototypage SoCLib respectent le protocole de communication VCI/OCP présenté en cours. On va donc re-écrire les modèles de simulation des deux composants matériels du TP1, pour qu'ils utilisent des ports de communication VCI plutôt que des ports FIFO. Ceci va permettre d'interconnecter plusieurs composants ''initiateurs'' et plusieurs composants ''cibles'' à travers un bus système. = 2 Architecture matérielle cible = L'architecture matérielle qu'on souhaite prototyper dans ce second TP instancie trois composants matériels : Les deux composants ''!VciGcdCoprocessor'' et ''!VciGcdMaster'' sont instanciés trois fois chacun. Ils ont des fonctionnalités identiques à celles des composants utilisés dans le premier TP, mais ces composants possèdent maintenant des ports de communication qui respectent le protocole VCI/OCP. A la différence du protocole FIFO, le protocole VCI introduit une dissymétrie entre les initiateurs et les cibles : Le composant ''!VciGcdMaster'' se comporte comme un initiateur, et contrôle non seulement le chargement des données vers le le composant ''!VciGcdCoprocesseur'' cible, mais contrôle également la récupération du résultat. Le troisième composant ''!VciVgsb'' (Virtual Generic System Bus) est un composant matériel modélisant un bus multi-maîtres, multi-cibles respectant le protocole VCI/OCP. [[Image(soclib_tp2_archi.png)]] Le composant ''!VciVgsb'' se comporte comme un bus, car il ne traite qu'une seule transaction à la fois. Si plusieurs initiateurs cherchent à envoyer un paquet commande, le contrôleur du bus sélectionne un initiateur (en respectant une priorité tournante de type ''round-robin''), il aiguille la commande vers la bonne cible en décodant les bits de poids fort de l'adresse, attend la réponse de la cible, et aiguille celle-ci vers le bon initiateur. La transaction (n+1) n'est traitée que lorsque la transaction (n) est entièrement terminée. = 3 Protocole VCI/OCP = Le protocol de communication VCI permet de construire des architectures matérielles multi-processeurs à memoire partagée. Dans ce type d'architecture, les différents composants matériels utilisent des transactions pour communiquer entre eux. Terminologie : * un ''flit'' est l'information qui peut être transférée en un seul cycle sur un interface VCI. * un ''paquet'' est une séquence de flits qui sont transmis de façon atomique à travers le réseau d'interconnexion. * une ''transaction'' est un couple (paquet commande / paquet réponse) Une transaction est initiée par un composant initiateur, qui envoie un paquet ''commande'', et est terminée par un composant cible, qui répond à la commande en renvoyant un paquet ''réponse''. == 3.1 Interconnection VCI == Dans la spécification VCI ''advanced'', il y a principalement deux types de commandes : * transaction CMD_WRITE : le paquet commande contient un ou plusieurs flits (à des adresses constantes ou consécutives). Le paquet réponse contient un seul flit pour acquitter la transaction. * transaction CMD_READ : le paquet commande contient un seul flit (à l'adresse du premier octet) et le nombre d'octets à lire est défini par le champs PLEN. Le paquet réponse contient un ou plusieurs flits suivant la longueur de la rafale. '''Question 1''' : à quoi sert le paquet réponse dans le cas d'une transaction d'écriture ? Dans une architecture à espace d'adressage partagé, n'importe quel initiateur est capable de communiquer avec n'importe quelle cible. La cible est désignée par les bits de poids fort de l'adresse. Le champs VCI ADDRESS doit donc être décodé par le (ou les) composant(s) matériel(s) qui réalise(nt) le sous-sytème d'interconnexion, pour aiguiller le paquet commande vers sa destination. De façon symétrique, le sous-système d'interconnexion doit décoder le champs VCI RSRCID pour aiguiller le paquet réponse vers l'initiateur concerné. '''Question 2''' : Pourquoi les différents types de sous-sytèmes d'interconnexion (bus, cross-bar, micro-réseaux, etc.) sont-ils conçus de telle sorte qu'ils utilisent des ressources matérielles séparées pour aiguiller les commandes et les réponses ? [[Image(soclib_tp2_vci_protocol.png)]] Un canal de communication VCI utilise donc deux sous-canaux : un sous-canal dans le sens (initiateur => cible) pour la commande et un sous-canal dans le sens (cible=>initiateur) pour la réponse. Il est intéressant de noter que chacun de ces deux sous-canaux respecte le protocole FIFO. Les signaux CMDVAL et CMDACK (resp. RSPVAL et RSPACK) correspondent aux signaux WOK et ROK du protocole FIFO pour le sous-canal commande (resp. réponse). == 3.2 Les signaux VCI == La figure ci-dessous détaille les signaux utilisés par le protocole VCI. [[Image(soclib_tp2_vci_signals.png)]] La plupart des champs VCI on des largeurs paramètrables (en nombre de bits) : * le paramètre '''N''' définit le nombre de bits du champs ADDRESS. Les adresses VCI sont des adresses octets, mais elles doivent être alignées sur des frontières de mot. * le paramètre '''B''' définit le nombre d'octets d'un mot de donnée VCI. Ce paramètre définit le largeur des trois champs WDATA, RDATA et BE. * le paramètre '''K''' définit le nombres de bits termettant de coder la longueur PLEN d'un paquet (en nombre d'octets). Nous n'utiliserons que des valeurs de PLEN multiples du paramètre '''B'''. * le paramètre '''S''' définit le nombre de bits du champs SRCID, qui permet de coder le numéro de l'initiateur VCI qui a démarré la transaction. Ce paramètre définit donc le nombre maximum d'initiateurs dans le système. * Le paramètre '''E''' définit le nombre de bits permettant de coder le type d'erreur dans le champs RERROR du paquet commande. `RERROR = 0` signifie "pas d'erreur". * Les deux paramètres '''T''' et '''P''' définissent le nombre de bits des deux champs TRDID et PKTID. Ces deux champs permettent d'étiqueter une commande VCI par une numéro de thread et/ou par un numéro de transaction. Ils peuvent être utilisés par un initiateur pour envoyer une commande (n+1) sans attendre d'avoir reçu la réponse à la commande (n). Bien sûr les valeurs de ces paramètres VCI doivent être les mêmes pour tous les composants matériels d'une même architecture, et doivent être définis dans la ''top-cell'' décrivant l'architecture générale du système. == 3.3 Modélisation des interfaces VCI == La généricité des interfaces de communication VCI est évidemment une souplesse très utile, puisqu'elle permet d'adapter le protocole aux besoins particuliers de chaque application embarquée (on n'a pas toujours besoin de 4 Goctets d'espace adressable). Mais elle crée une difficulté pour la modélisation des composants matériels, puisqu'il faut écrire des modèles de simulation génériques, capable de s'adapter à différentes largeurs des champs adresse ou donnée. On utilise pour cela la techique des ''templates'' du langage C++ : on regroupe toutes les valeurs de ces paramètres dans un objet C++ de type `VciParams`, qui est utilisé comme paramètre ''template'' par tous les composants matériels qui possèdent des ports de communication VCI. (voir fichier ''vci_param.h''). Comme on l'a fait pour le canal de communication FIFO, on définit trois objets, qui facilitent l'écriture des modèles de simulation CABA : * l'objet `VciSignals` regroupe tous les signaux (commande et réponse) d'un canal VCI. (voir fichier ''vci_signals.h'') * l'objet `VciInitiator` regroupe tous les ports utilisés par un initiateur VCI pour émettre une commande, et recevoir la réponse. (voir fichier ''vci_initiator.h'') * l'objet `VciTarget` regroupe tous les ports utilisés par une cible VCI pour émettre une réponse, et recevoir une commande. (voir fichier ''vci_target.h'') Ces trois objets possèdent évidemment un paramètre template de type `VciParams`. = 4 Outillage logiciel = Dans cette section, on présente différentes classes C++ définies par la plate-forme de prototypage virtuel SoCLib pour faciliter et simplifier l'écriture des modèles de simulation. == 4.1 Indexation des composants VCI == On assigne à tout composant matériel possédant un port VCI (composant ''cible'' ou composant ''initiateur'') un index permettant de l'identifier de façon unique. Cet index peut être éventuellement structuré (on parle d'index composite) si l'architecture est structurée en ''clusters''. Un index composite est la concaténation d'un index global (le numéro de cluster) et d'un index local (à l'intérieur d'un cluster). La plate-forme SoCLib définit la classe C++ ''!IntTab'' pour représenter ces index composites. (voir fichier ''int_tab.h''). Dans ce TP, on utilisera un seul niveau d'indexation, mais il faut utiliser l'objet ''!IntTab'' pour indexer les composants VCI. == 4.2 Segmentation de l'espace addressable == Dans une architecture à mémoire partagée, on assigne à tout composant ''cible'' un (ou plusieurs) segment(s) dans l'espace adressable. Un segment est une ''tranche'' de l'espace adressable. Il possède donc une adresse de base, et une longueur (en nombre d'octets). La longueur d'un segment peut être très variable : de quelques dizaines d'octets pour un périphériques adressable, à quelques Moctets pour un contrôleur mémoire. C'est en analysant les bits de poids fort de l'adresse que le sous-système d'interconnexion détermine à quel segment appartient l'adresse, et donc vers quelle cible un paquet commande doit être aiguillé. Le découpage de l'espace adressable en segments et l'assignation de ces segments aux différentes cibles est donc un caractéristique globale de l'architecture, et doit donc être définie dans la ''top-cell'' décrivant l'architecture générale du système. La plate-forme SoCLib définit la classe C++ ''!MappingTable'' permettant de centraliser dans un même objet logiciel toutes les informations concernant la segmentation de l'espace addressable, et la correspondance entre une cible VCI (identifiées par son index), et un segment. (voir fichiers ''mapping_table.h'' et ''segment.h''). Un segment est donc un objet caractérisé par 5 informations : * un nom * une adresse de base * une taille (en octets) * l'index de la cible VCI à laquelle il est assigné * un attribut de cachabilité Pour plus de détails, vous pouvez consulter le site WEB du projet SoCLib : [http://www.soclib.fr/trac/dev/wiki/Component/MappingTable]. == 4.3 Décodage de l'adresse == Le champs ADDRESS de la commande VCI est décodé à deux endroits : * les bits de poids faibles sont décodés par les cibles VCI pour déterminer sur quel octet porte la commande. * les bits de poids fort sont décodés par le sous-système d'interconnexion pour déterminer l'index de la cible et router le paquet commande vers la cible concernée. Pour faciliter ce décodage, la plate-forme SoCLib définit les classe C++ ''!AddressDecodingTable'' (voir fichier ''address_decoding_table.h'') et ''!AddressMaskingTable'' (voir fichier ''address_masking_table.h'') == 4.4 Allocation de tableaux == On a parfois besoin d'utiliser des tableaux d'objets complexes. Par exemple, le composant générique ''!VciVgsb'' possède un nombre variable de ports VCI initiateur, et un nombre variable de ports VCI cibles. Ces ports sont donc définis comme des tableaux de ports. Pour pouvoir nommer chacun des éléments d'un tableau, la plate-forme SoCLib définit un mécanisme générique d'allocation et de désallocation de tableaux (voir fichier ''alloc_elems.h''). = 5 Travail à réaliser = L'archive [attachment:soclib_tp2.tgz soclib_tp2.tgz] contient différents fichiers dont vous aurez besoin pour ce TP. Créez un répertoire de travail spécifique TP2, recopiez l'archive dans ce répertoire TP2, et décompressez-la: {{{ $ tar xzvf soclib_tp2.tgz }}} Cette archive contient les fichiers généraux suivants, extraits de la plate-forme SoCLib : * `vci_param.h` : definition des paramètres VCI. * `vci_signals.h` : definition d'un canal VCI. * `vci_initiator.h` : définition d'un port VCI initiateur. * `vci_target.h` : définition d'un port VCI cible. * `int_tab.h` : définition des index composites. * `segment.h` : définition d'un segment de l'espace adressable. * `segment.cpp` : implémentation des méthodes du segment. * `mapping_table.h` : définition de la mapping table. * `mapping_table.cpp` : implémentation des méthodes de la mapping table. * `address_decoding_table.h` : table indexée par une partie de l'adresse. * `address_decoding_table.cpp` : implémentation des méthodes de la table indexée. * `alloc_elems.h` : allocation de tableaux d'objets complexes * `exception.h` : traitement et report d'erreurs L'archive contient également les fichiers suivants : * `gcd.h` : ce fichier décrit les registres adressables du coprocesseur GCD (fichier complet) * `vci_gcd_master.h` : définition du composant `VciGcdMaster` (fichier complet) * `vci_gcd_master.cpp` : méthodes associées (fichier incomplet) * `vci_gcd_coprocessor.h` : définition du composant `VciGcdCoprocessor`. (fichier complet) * `vci_gcd_coprocessor.cpp` : méthodes associées (fichier incomplet) * `vci_vgsb.h` : définition du composant `VciVgsb`. (fichier complet) * `vci_vgsb.cpp` : méthodes associées (fichier complet) * `tp2_simple_top.cpp` : top-cell d'une architecture simple à deux composants (fichier incomplet) == 5.1 Composant ''!VciGcdCoprocessor'' == Le composant ''!VciGcdCoprocessor'' est un périphérique adressable, et doit donc être modélisé comme une cible VCI. Il possède un seul port de type ''!VciTarget'', et 4 registres (ou pseudo-registres) implantés dans l'espace addressable, qui peuvent donc - en principe - être lus ou écrits par n'importe quel initiateur du sytème. Chacun de ces registres a une largeur de 4 octets. Par conséquent, le segment occupé par ce périphérique dans l'espace adressable a une longueur de `4*4` = 16 octets. Pour tous les périphériques adressables, on simplifie le décodage des adresses par le matériel, en imposant la contrainte que l'adresse de base du segment associé au périphérique soit un multiple de sa longueur (on dit que le segment est ''aligné''). La carte des registres est définie comme suit : || '''Nom du registre''' || '''Offset''' || '''Mode''' || || GCD_OPA || 0x0 || Read / Write || || GCD_OPB || 0x4 || Write Only || || GCD_START || 0x8 || Write Only || || GCD_STATUS || 0xc || Read Only || Comme pour tout composant contenant des registres addressables, on définit dans un fichier séparé ''gcd.h'' les mnémoniques correspondant aux numéros des registres, car cette information est utilisée non seulement par le composant ''!VciGcdCoprocesseur'' (pour décoder les adresses), mais également par le composant ''!VciGcdMaster'' (pour générer les adresses). * Une commande d'écriture vers le registres GCD_OPA (resp. GCD_OPB) permet d'écrire l'opérande A (resp. opérande B). * Une commande de lecture du registre GCD_OPA retourne la valeur du résultat si le coprocesseur a fini son calcul. Elle retourne une valeur non significative si le coprocesseur n'a pas fini son calcul. * Une commande de lecture du registre GCD_STATUS retourne la valeur 0 lorsque le coprocesseur est dans l'état ''IDLE''. Elle retourne une valeur différente de 0 si le coprocesseur n'a pas fini son calcul. * Une commande d'écriture vers le pseudo-registre GCD_START déclenche le calcul du PGCD portant sur les valeurs présentes dans les registres GCD_OPA et GCD_OPB. La donnée correspondant à cette écriture (champs VCI WDATA) n'est écrite nulle part. Comme vous pouvez le constater dans la figure ci-dessous, le coprocesseur GCD contient deux automates fonctionnant en parallèle : * l'automate ''vci_fsm'' contrôle l'interface VCI : il répond aux commandes qu'il reçoit, en écrivant dans le registre concerné s'il sagit d'une écriture, ou en renvoyant la valeur demandée s'il s'agit d'une lecture. * l'automate ''exe_fsm'' contrôle l'exécution de la boucle de calcul du PGCD. [[Image(soclib_tp2_coprocessor.png)]] Le fichier `vci_gcd_coprocessor.h` contient une définition complête du composant `VciGcdCoprocessor`. Il n'a pas besoin d'être modifié, mais vous devez le lire attentivement pour modifier le fichier `vci_gcd_coprocessor.cpp`, qui contient une description incomplête des méthodes associées à ce composant. Complétez le code des méthodes `transition()` et `genMoore()`. '''Question 3''' : Pourquoi faut-t-il deux automates séparés pour contrôler l'interface VCI et pour contrôler le calcul du PGCD proprement dit ? '''Question 4''' : Dans la fonction de transition, on a écrit le code définissant les transitions de l'automate ''vci_fsm'' après le code définissant les transitions de l'automate ''exe_fsm'', bien que les transitions de l'automate ''exe_fsm'' dépendent de l'état de l'automate ''vci_fsm''. On aurait aussi bien pu décrire ces deux automates dans l'ordre inverse. Quelle mécanisme du langage SystemC utilise-t-on pour rendre l'ordre de description des automates indifférent? Le coprocesseur GCD doit signaler une erreur dans les trois cas suivants : * le coprocesseur reçoit une commande VCI de longueur supérieure à un flit, * l'adresse reçue n'appartient pas au segment qui a été défini pour le coprocesseur, * le mode d'accès (Read ou Write) ne respecte pas les contraintes définies dans la carte des registres. '''Question 5''' : comment sont traitées les erreurs dans ce modèle de simulation? à quoi servent ces vérifications ? == 5.2 Composant ''!VciGcdMaster'' == Le composant `VciGcdMaster` est un initiateur VCI, qui joue le rôle d'un processeur, mais qui n'est pas programmable: Il exécute un unique ''programme'' qui est une une boucle infinie dans laquelle on effectue successivement les actions suivantes: 1. calcul de deux valeurs aléatoires (entiers positifs codés sur 32 bits) 1. écriture de l'opérande OPA dans le registre GCD_OPA du coprocesseur GCD. 1. écriture de l'opérande OPB dans le registre GCD_OPB du coprocesseur GCD. 1. écriture dans le pseudo-registre GCD_START du coprocesseur GCD. 1. scrutation du registre GCD_STATUS du coprocesseur GCD, jusqu'à obtenir une valeur nulle (calcul terminé).. 1. lecture du résultat dans le registre GCD_OPA du coprocesseur GCD. 1. affichage des résultats... et on recommence. Pour accéder au coprocesseur GCD, le composant ''!VciGcdMaster'' a besoin de l'adresse de base du segment de l'espace adressable qui a été assigné au coprocesseur GCD. Le composant ''!VciGcdMaster'' étant un automate cablé (non programmable), on considère que cette adresse est également "câblée". Elle est donc définie comme un paramètre du constructeur. La figure ci-dessous décrit la structure de l'automate de contrôle du composant ''!VciGcdMaster''. [[Image(soclib_tp2_master.png)]] Chaque transaction VCI nécessite deux états dans l'automate: un premier état pour envoyer la commande (on reste dans cet état tant qu'on a pas reçu confirmation que la commande a été acceptée), et un second état dans lequel on attend la réponse (on reste dans cet état tant qu'on a pas reçu une réponse valide). Le fichier `vci_gcd_coprocessor.h` contient une description complète du composant `VciGcdMaster`. Il n'a pas besoin d'être modifié, mais vous devez le lire attentivement pour modifier le fichier `vci_gcd_coprocessor.cpp`, qui contient une description incomplète des méthodes associées à ce composant. Complétez le code des méthodes `transition()` et `genMoore()`. == 5.3 Architecture minimale == Pour valider les modèles de simulation des composants `VciGcdMaster` et `VciGcdCoprocessor`, on construit une architecture minimale ne contenant qu'un seul initiateur et une seule cible. Comme il n'y a qu'un initiateur, il ne peut pas y avoir de conflit d'accès au bus, et comme il n'y a qu'une seule cible, il n'y a pas besoin de décoder les bits de poids fort de l'adresse pour router la commande. On n'a donc pas besoin d'utiliser le composant 'vci_vgsb'. [[Image(soclib_tp2_simple_archi.png)]] En vous inspirant de ce que vous avez fait dans le TP1, complétez le fichier ''tp2_simple_top.cpp'' qui vous est fourni, en précisant : * les caractéristiques du segment mémoire associé au coprocesseur GCD. * les valeurs des paramètres VCI (largeurs des champs) dans l'objet `vci_param`. * les valeurs des arguments des constructeurs des deux composants. On prendra pour valeurs des paramètres VCI les valeurs définies en commentaire dans le fichier ''tp2_simple_top.cpp''. == 5.4 Compilation et génération du simulateur == Il faut maintenant compiler les différents fichiers pour générer le simulateur. On va utiliser la même méthode que dans le TP1, mais il y a une difficulté supplémentaire, à cause du paramètre template `vci_param` des composants `VciGcdMaster`, `VciGcdCoprocessor`. Dans un contexte de compilation séparée, il est nécessaire de définir explicitement la valeur de ce paramètre dans chacun des fichiers source avant de lancer la génération du fichier objet associé. Pour cela, il faut rajouter la ligne suivante à la fin des fichiers `vci_gcd_master.cpp` et `vci_gcd_coprocessor.cpp` : {{{ template class VciGcdMaster >; }}} (pensez à changer le nom de la classe pour `vci_gcd_coprocessor.cpp`. Ceci étant fait, écrivez le Makefile permettant la génération du fichier exécutable `simple_simulator.x`. N'oubliez pas d'inclure dans la liste des fichiers objets les fichiers `mapping_table.o`, `segment.o`, `address_decoding_table.o`, `address_masking_table.o` (en plus des fichiers objet correspondant aux deux composants matériels Le lancement du simulateur doit vous fournir les mêmes résultats que ceux obtenus dans le TP1, puisque les calculs effectués sont les mêmes (seul le protocole de communication a changé). == 5.5 Architecture multi-maitres / multi-cibles == L' architecture interne du composant `VciVgsb` est décrite dans la figure ci-dessous. Le `bus` des commandes VCI, et le `bus` des réponses VCI sont modélisés par des multiplexeurs. Ces multiplexeurs sont commandés par un automate à trois états qui réalise une priorité tournante entre les initiateurs. Comme vous pouvez le constater sur le schéma, ce composant se comporte comme un automate de Mealy, puisque - une fois le bus alloué à un initiateur - les signaux de sortie dépendent combinatoirement des signaux d'entrée. La latence minimale d'une transaction rafale de N mots VCI est de (N+1) cycles, dans le cas où la cible répond immédiatement. Du point de vue latence (durée d'une transaction) et bande passante (nombre d'octets transférés par cycle), ce composant se comporte comme le PIbus. [[Image(soclib_tp2_bus.png)]] En vous inspirant du fichier `tp2_simple_top.cpp` de la question précédente, écrivez le fichier `tp2_multi_top.cpp`, qui décrit l'architecture à 7 composants décrite au début de ce TP. Vous ferez en sorte que l'adresse utilisée par le maitre (i) lui permette de communiquer avec le coprocesseur (i). N'oubliez donc pas de définir 3 segments différents pour les trois coprocesseurs. Il faut également ajouter à la fin du fichier `vci_vgsb.cpp` la ligne permettant de définir la valeur du paramètre template `vci_param` : {{{ template class VciVgsb >; }}} Modifiez le Makefile pour générer le fichier exécutable `multi_simulator.x`, en n'oubliant pas d'inclure le fichier `vci_vgsb.o` dans l'ensemble des fichiers objet. Le lancement du simulateur doit vous fournir une trace d'exécution qui entrelace les compte-rendus des trois initiateurs qui s'exécutent en parallèle. Chaque initiateur commande un seul coprocesseur, et la seule ressource partagée est le bus de communication. = 6 Compte-rendu = Vous devez rédiger un compte-rendu pour ce TP résumant vos expériences, et on vous demandera une démonstration de votre simulateur au début du TP de la semaine suivante...