wiki:SoclibCourseTp1

TP1: "Prototypage Virtuel avec SoCLib"

(alain.greiner@…)

1. Objectif

L'objectif de ce premier TP est d'illustrer - sur un exemple très simple ne comportant que deux composants matériels - les principes de la modélisation SystemC au niveau d'abstraction CABA (Cycle Accurate, Bit Accurate). Ce type de modélisation s'appuie sur la théorie des automates d'états synchrones communicants (CFSM), et permet d'utiliser des techniques d'ordonnancement statique pour accélérer la simulation. Il est indispensable de prendre le temps de lire le document décrivant les règles d'écriture des modèles CABA ici,

2. Architecture matérielle

L' architecture matérielle que vous devez modéliser est décrite ci-dessous : Le premier composant est un coprocesseur cablé (c'est à dire non programmable) qui calcule le PGCD (plus grand commun diviseur) de deux nombres entiers positifs A et B, codés sur 32 bits. Le second composant est chargé de transmettre les valeurs des opérandes A et B au coprocesseur, et de récupérer le résultat. Ces deux composants matériels fonctionnent en parallèle, et communiquent entre eux par des canaux de communication de type FIFO.

Conformément aux principes CFSM, ces deux composants sont donc modélisés par deux automates synchrones (c'est à dire cadencés par la même horloge CK). L'écriture dans tous les registres du système se fait sur le front montant de CK. Ils sont initialisés par le même signal RESETN, actif à l'état bas.

2.1 Canal de communication FIFO

Le canal de communication FIFO implante un protocole de communication très simple supportant le contrôle de flux.

Chacune des deux entités communicantes considère que son interlocuteur est une simple FIFO. Une FIFO est une mémoire double accès de type First-In-First-Out sans adressage explicite. Le producteur peut écrire dans la FIFO en activant le signal W. L'écriture est effective lorsque la FIFO n'est pas pleine (WOK peut être considéré comme un signal d'état signifiant FIFO non pleine). Le consommateur peut lire une donnée dans la FIFO en activant le signal R. La consommation est effective lorsque la FIFO n'est pas vide (ROK peut être considéré comme un signal d'état signifiant FIFO non vide).

Attention : Il n'y a pas de mécanisme de handshacking : le producteur n'a pas besoin de consulter le consommateur avant d'envoyer un ordre d'écriture (c'est à dire W = true). Il se contente d'activer le signal W et de tester le signal WOK pour savoir si la donnée a été acceptée par le consommateur. De même, le consommateur n'a pas besoin de consulter le producteur avant d'envoyer un ordre de lecture (c'est à dire R = true). Il se contente d'activer le signal R, et de tester le signal ROK pour savoir si la donnée reçue est valide. Par conséquent, une donnée est effectivement transmise à chaque cycle où les deux signaux R/WOK et W/ROK ont simultanément la valeur true. Ce protocole supporte un débit maximal d'une donnée par cycle, et permet a chaque interlocuteur de forcer l'autre à attendre quand lui-même n'est pas prêt.

Un des avantages de ce protocole est que les deux composants communicants se comportent tous les deux comme des automates de Moore : Il n'y a évidemment qu'un seul émetteur par signal, et la valeur du signal ne dépend que de l'état interne de l'émetteur.

2.2 Composant fifo_gcd_coprocessor

L'algorithme de calcul du PGCD implanté par cet automate cablé peut être décrit par le code C suivant :

uint32_t pgcd( uint32_t opa, uint32_t opb)
{
    while (opa != opb )
        {
        if ( opa > opb ) opa = opa - opb;  
        if ( opa < opb ) opb = opb - opa 
        }
    return( opa );
}

Le chemin de donnée (matériel) permettant de réaliser ce calcul doit donc comporter deux registres r_opa et r_opb pour stocker les valeurs opa et opb qui évoluent au cours du calcul, ainsi qu'un comparateur et un soustracteur sur 32 bits.

Par ailleurs l'automate cablé reçoit les deux valeurs des opérandes OPA et OPB sur son port FIFO d'entrée, et renvoie le résultat sur son port FIFO de sortie.

Finalement, l'automate (matériel) qui contrôle le composant fifo_gcd_coprocesseur exécute un boucle infinie, dans laquelle il effectue successivement les 4 opérations suivantes:

  1. lecture de l'opérande A sur son port FIFO d'entrée
  2. lecture de l'opérande B sur son port FIFO d'entrée
  3. calcul effectif du PGCD
  4. écriture du résultat sur son port FIFO de sortie

Ces quatres opérations ont des durées d'exécution variables, puisque le nombre de cycles pour effectuer le calcul (étape 3) dépend de la valeur des opérandes, et que les opérations de communications (étapes 1, 2 ou 4) ont des durées qui dépendent de la disponibilité du composant fifo_gcd_master.

Outre le registre d'état de l'automate r_fsm, cet automate contrôle donc les écritures dans deux autres registres r_opa et r_opb utilisés pour le calcul :

  • Dans l'état READ_OPA (resp. READ_OPB), on écrit dans le registre r_opa (resp r_opb) la valeur de l'opérande OPA (resp. OPB) lue sur le port FIFO d'entrée (champs p_in.data). On ne sort de cet état que si la donnée est valide (condition p_in.rok = true).
  • Dans l'état WRITE_RES, on écrit le contenu du registre r_opa sur le port FIFO de sortie p_out.data. On ne sort de cet état que si la donnée est acceptée (condition p_out.wok = true).
  • Dans l'état COMPARE, on effectue la comparaison entre les contenus des registres r_opa et r_opb. On ne reste que qu'un cycle dans cet état, puisque la comparaison ne dépend de rien d'autre que les valeurs contenues dans les registres r_opa et r_opb. Ces registres ne sont pas modifiés dans cet état, mais l'état suivant dépend du résultat de la comparaison.
  • Dans l'état DECR_A (resp. DECR_B), on écrit le dans le registre r_opa (resp. r_opb). On ne reste qu'un cycle dans ces états, puisque la décrémentation ne dépend d'aucune condition extérieure.

2.3 Composant fifo_gcd_master

Ce composant matériel effectue le travail normalement effectué par le logiciel s'exécutant sur un processeur programmable, consistant à définir les valeurs des deux opérandes, à transmettre ces valeurs au coprocesseur, à récupérer le résultat calculé par le coprocesseur, et à afficher ce résultat sur un terminal. L'utilisation de processeurs programmables suppose qu'on est capable de déployer le code binaire exécutable par le processeur programmable sur l'architecture matérielle simulée. Ce problème sera traité dans les TPs suivants, mais dans ce premier TP, on se contente d'utiliser un automate cablé, qui exécute en boucle le programme suivant:

  1. génération (pseudo-aléatoire) de deux valeurs OPA et OPB.
  2. écriture de l'opérande OPA sur son port FIFO de sortie.
  3. écriture de l'opérande OPB sur son port FIFO de sortie
  4. lecture du du résultat sur son port FIFO d'entrée.
  5. affichage des valeurs des opérandes et du PGCD sur le terminal.

Pour modéliser la génération aléatoire, on utilise la fonction rand() fournie par la libc de la station de travail qui exécute la simulation. On génère évidemment des valeurs aléatoires différentes à chaque itération de la boucle, mais pour faciliter le deboguage, on garantit un fonctionnement reproductible, en contrôlant la valeur initiale du générateur aléatoire grace à la fonction srand() utilisée dans le constructeur du modèle.

Le composant fifo_gcd_master est donc un composant matériel paramètrable (un paramètre permettant de contrôler la séquence de valeurs aléatoires), modélisé comme un automate à 5 états :

Outre le registre d'état de l'automate r_fsm, cet automate contrôle 5 autres registres : les registres r_opa, r_opb, et r_res permettent de stocker respectivement les deux opérandes et le résultat du calcul. Le registre r_cyclecount est incrémenté à chaque cycle, et contient donc la date (en nombre de cycles) depuis l'initialisation du système. Le registre r_iterationcount est incrémenté à chaque itération, et contient donc le numéro de l'itération courante.

  • dans l'état RANDOM on écrit les valeurs pseudo-aléatoires OPA et OPB dans les registres r_opa et r_opb. On ne reste qu'un cycle dans cet état.
  • dans l'état WRITE_OPA (resp. WRITE_OPB) on écrit le contenu du registre r_opa (resp r_opb) sur le port FIFO de sortie (champs p_out.data). On ne sort de ces états que si la donnée est acceptée (condition p_out.wok = true).
  • dans l'état READ_RES, on écrit dans le registre r_res la valeur lue sur le port FIFO d'entrée (champs p_in.data). On ne sort de cet état que si la donnée lue est valide (condition p_in.rok = true).
  • Dans l'état DISPLAY, on ne modifie pas le contenu des registres r_opa, r_opb, et r_res, mais on affiche la date courante ainsi que les valeurs des opérandes et du résultat sur le terminal standard de la station de travail qui exécute la simulation. On ne reste qu'un cycle dans cet état.

Question : Compte-tenu de l'algorithme de calcul du PGCD implémenté par le composant fifo_gcd_coprocessor, que se passe-til si un des deux opérandes transmis au coprocesseur a la valeur 0? Comment peut-on modifier le composant fifo_gcd_master pour que ceci ne se produise jamais?

3. Travail à réaliser

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

$ tar xzvf soclib_tp1.tgz

Cette archive contient les fichiers suivants :

  • fifo_signals.h : fichier définissant la classe C++ FifoSignal représentant les signaux d'un canal FIFO. Ce fichier est complet et ne doit pas être modifié.
  • fifo_ports.h : fichier définissant les classes C++ FifoInput et FifoOutput représentant les ports d'accès à un canal FIFO. Ce fichier est complet et ne doit pas être modifié.
  • fifo_gcd_master.h : fichier définissant la classe C++ FifoGcdMaster représentant le modèle du composant master. Ce fichier est complet et ne doit pas être modifié.
  • fifo_gcd_master.cpp : fichier contenant l'implémentation C++ des méthodes utilisées par le composant master. Ce fichier est complet et ne doit pas être modifié.
  • fifo_gcd_coprocessor.h : fichier définissant la classe C++ FifoGcdCoprocessor représentant le modèle du composant coprocesseur. Ce fichier ne contient qu'une carcasse vide et devra être complété.
  • fifo_gcd_coprocessor.cpp : fichier contenant l'implémentation C++ des méthodes utilisées par le composant coprocesseur. Ce fichier ne contient qu'une carcasse vide et devra être complété.
  • tp1_top.cpp : fichier contenant la classe C++ décrivant l'architecture matérielle du système. Ce fichier est partiellement incomplet, et devra être complété.

3.1 Ecriture du modèle CABA du coprocesseur

Pour une présentation détaillée des règles d'écriture des modèles de simulation au niveau CABA, vous pouvez consulter la documention disponible en ligne sur le site sur le site WEB du projet SoCLib : https://www.soclib.fr/trac/dev/wiki/WritingRules/Caba.

Le modèle de simulation d'un composant matériel (appelé module en SystemC) nécessite la définition d'une classe C++ dont le nom correspond au nom du module matériel modélisé. En vous inspirant du code fourni pour le composant master, complêter les deux fichiers fifo_gcd_coprocessor.h et fifo_gcd_coprocessor.cpp.

Comme pour tout modèle CABA SoCLib, les variables membres de la classe FifoGcdCoprocessor sont de trois types :

  • ports : les variables membres représentant les ports d'entrée sortie sont de type sc_core::sc_in ou sc_core::sc_out, ou des objets plus complexe (possédant éventuellement un paramètre template) représentant des regroupements de plusieurs ports élémentaires : c'est la cas des types soclib::caba::FifoOutput<typename> et soclib::caba::FifoInput<typename> utilisés pour accéder à un canal FIFO. Pour des raisons de lisibilité, il est recommandé que les noms des ports soient préfixés par p_. On définira 2 ports simples (p_ck, p_resetn), et deux ports FIFO (p_in et p_out). Ce sont évidemment des variables publiques.
  • registres : les variables membres représentant les registres sont de type sc_core::sc_signal. Pour des raisons de lisibilité, il est recommandé que les noms des registres soient préfixé par r_. On définira trois registres r_fsm, r_opa et r_opb. Ce sont des variables privées.
  • constantes : ces variables membres sont en fait des constantes permettant de stocker les valeurs des paramètres structurels définis comme arguments du constructeur pour les composants matériels génériques. Ces variables membres sont initialisées dans le constructeur, et leur valeur reste constante au cours de la simulation. Pour des raisons de lisibilité, il est recommandé que les noms des constantes soient préfixé par m_. Ces variables sont toujours privées. Le coprocesseur n'étant pas paramètrable, on ne définira aucune variable de ce type.

Le constructeur du coprocesseur GCD ne possède qu'un seul argument, qui est le nom d'instance (on a besoin d'un nom d'instance, car il est possible d'avoir plusieurs instances du même coprocesseur dans une architecture). Ce nom d'instance est de type sc_core::sc_module_name, et il est stoké dans une variable membre de la classe sc_module, qui est une classe générique dont héritent tous les modèles de composant SoCLib.

Enfin, comme tous les modèles CABA de SoCLib, le coprocesseur GCD possède des fonctions membres définissant le comportement du composant, qui sont de trois types :

  • la fonction transition() est sensible au front montant du port d'entrée CK, et permet de calculer la valeur future des registres en fonction de la valeur courante des registres et des valeurs présentes sur les autres ports d'entrée.
  • la fonction genMoore() est sensible au front descendant du port d'entrée CK, et permet de calculer la valeur des ports de sortie qui ne dépendent que des valeurs stockées dans les registres.
  • les fonctions genMealy() (une ou plusieurs fonctions) sont sensibles au front descendant du port CK. De plus chaque fonction est sensible à un ensemble particulier de port d'entrée. Elle permettent de calculer la valeur des ports de sorties qui dépendent de façon combinatoire d'un ou plusieurs ports d'entrée.

Les noms de fonction ne sont pas imposés, mais il est recommandé de respecter les noms proposés ci-dessus. Le coprocesseur GCD se comportant globalement comme un automate de Moore, on n'a pas besoin de définir de fonctions de type genMealy().

Les architectures matérielles modélisées avec SoCLib peuvent comporter plusieurs dizaines de modèles différents. Pour réduire les temps de compilation la plate-forme de modélisation SoCLib exploite le principe de la compilation séparée: Chaque modèle de composant matériel est compilé séparément.

A titre d'exemple, compilez le modèle du composant FifoGcdMaster, en utilisant le compilateur g++. Il faut lancer la commande suivante dans votre répertoire de travail TP1 :

$ g++ -Wno-deprecated -fpermissive -std=gnu++0x -I. -I/users/outil/dsx/cctools/include -m32 -c  fifo_gcd_master.cpp

Cette commande doit créer le fichier objet fifo_gcd_master.o dans le répertoire TP1.

Vous pouvez utiliser la même commande (en changeant les noms des fichiers) pour compiler... et déboguer le modèle du composant FifoGcdCoprocessor.

3.2 Ecriture du modèle CABA de la top-cell

On appelle généralement top-cell la description structurelle decrivant l'interconnexion des différents composants matériels constituant l'architecture matérielle qu'on souhaite simuler. Editez le fichier tp1_top.cpp, qui décrit l'architecture, ainsi que les directives de simulation. Ce fichier est volontairement incomplet pour vous obliger à analyser en détail les différentes sections :

  • arguments : On définit dans cette section les arguments qui pourront être passés en ligne de commande lors du lancement de la simulation. On prévoit ici de passer en premier argument le nombre de cycles à simuler, et comme deuxième argument la valeur d'initialisation du générateur aléatoire utilisé dans le composant master. cette section n'a pas besoin d'être modifiée.
  • Signals : On définit dans cette section les différents signaux qui seront utilisés pour connecter entre eux les ports d'entrée sortie des différents composants. On a besoin de 4 signaux : les deux signaux signal_ck et signal_resetn devront être connectés aux ports correspondants des deux composants matériels. Les deux signaux signal_fifo_m2c et signal_fifo_c2m sont des signaux composites représentant les deux canaux de communication entre les deux composants. On utilise un type générique, en ce sens que le type de la donnée transférée à chaque cycle doit être défini par un paramètre template : dans le cas présent, on transfère un entier 32 bits uint32-t. Le signal signal_clk est également un objet complexe de type sc_core::sc_clock, dont on peut définir la période (sc_time(1,SC_NS) signifie 1 ns), ainsi que le rapport cyclique. Vous devez ajouter le signal manquant dans cette section.
  • Components : Dans cette section on appelle les constructeurs des différents composants instanciés, en définissant les paramètres. Le nom d'instance est un paramètre obligatoire. Le composant master possède un second paramètre qui est la valeur d'initialisation du générateur aléatoire.
  • Net-List : On décrit dans cette section les connexions entre les composants. La syntaxe utilisée pour connecter un signal s au port p d'un composant c est : c.p(s). Vous devez ajouter les connexions concernant les ports du coprocesseur.
  • Simulation : Cette section décrit les directives de simulation. Le mécanisme de RESET étant synchrone, On commence par exécuter un cycle en forçant signal_resetn à l'état bas (actif), pour initialiser les registres internes de tous les composants, puis on exécute le nombre de cycles spécifié en forçant signal_resetn à l'état haut (inactif).

Vous pouvez compiler ce fichier tp1_top.cpp pour générer le fichier objet correspondant en utilisant la commande:

$ g++ -Wno-deprecated -fpermissive -std=gnu++0x -I. -I/users/outil/dsx/cctools/include -m32 -c  tp1_top.cpp

Cette commande doit créer le fichier objet tp1_top.o dans le répertoire TP1.

3.3 génération et lancement du simulateur

Vous pouvez maintenant créer le programme exécutable simulator.x en effectuant l'édition de liens entre les trois fichiers objet précédemment créés :

$ g++ -Wno-deprecated -fpermissive -m32 -L. -L/users/outil/dsx/cctools/lib-linux -o simulator.x fifo_gcd_master.o fifo_gcd_coprocessor.o tp1_top.o -lsystemc 2>&1 

Cette commande doit créer le fichier objet simulator.x dans le répertoire TP1.

On lance l'exécution du simulateur pour 10000 cycles avec la commande :

$ ./simulator.x 10000

Quelle est la duréee moyenne d'une itération?

Regroupez toutes les commandes de compilation dans un fichier Makefile, en explicitant les dépendances entre les fichiers.

3.4 Simulation avec SystemCASS

Les modèles de simulation respectant le style CABA disponibles dans SoCLib permettent une simulation rapide, en utilisant le moteur de simulation SystemC2.0 fourni par le consortium OSCI, puisque dans le cas où tous les composants matériels se comportent comme des automates de Moore, les deux fonctions transition et genMoore de chaque composant ne sont exécutées qu'une seule fois par cycle.

Le moteur de simulation SystemC2.0 (fourni par le consortium OSCI) utilise une technique d'ordonnancement dynamique (avec gestion d'un échéancier), qui a le mérite d'être très générale, et de s'adapter à n'importe quel style d'écriture des modèles SystemC. Mais la gestion de cet échéancier reste coûteuse en temps de calcul, et il est possible d'accélérer fortement la simulation en utilisant le moteur de simulation SystemCASS (développé par le laboratoire LIP6). En effet, SystemCASS exploite les caractéristiques particulières des modèles SoCLib CABA pour mettre en oeuvre une technique d'ordonnancement statique (sans échéancier).

Pour utiliser SystemCASS, il n'est pas nécessaire de modifier le code SystemC des composants instanciés, ni le code SystemC décrivant la top_cell, mais il faut recompiler l'ensemble des fichiers sources, en modifiant les chemins d'accès aux fichiers inclus et aux bibliothèques de SystemC. La génération des fichiers objets utilise la commande suivante :

$ g++ -Wno-deprecated -fpermissive -I. -I/users/outil/dsx/systemcass/include -m32 -c  filename.cpp

La génération de l'exécutable utilise la commande suivante :

$ g++ -Wno-deprecated -fpermissive -m32 -L. -L/users/outil/dsx/systemcass/lib -o  fast_simulator.x fifo_gcd_master.o fifo_gcd_coprocessor.o tp1_top.o -lsystemc -ldl 2>&1

Modifiez le fichier Makefile de la question précédente pour générer un exécutable fast_simulator.x et comparez les vitesses des deux simulateurs.

4. Compte-rendu

Vous devez rédiger un compte-rendu décrivant les étapes importantes de création de votre simulateur et faire une démonstration de votre simulateur.

Last modified 3 years ago Last modified on Oct 1, 2021, 11:55:51 AM

Attachments (5)

Download all attachments as: .zip