Changes between Initial Version and Version 1 of 2010CaoTme5


Ignore:
Timestamp:
Apr 8, 2010, 5:56:08 PM (15 years ago)
Author:
jpc
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • 2010CaoTme5

    v1 v1  
     1{{{
     2#!html
     3<h1>TME 7 : Simulation logico-temporelle</h1>
     4}}}
     5[[PageOutline]]
     6
     7= Objectif =
     8
     9On  souhaite réaliser  dans  ce TME  un  petit simulateur  logico-temporel, permettant  de
     10simuler un réseau  Booléen temporisé, où les expressions  Booléennes sont représentées par
     11des arbres EBM (voir TME3).
     12
     13Les  simulateurs  à événements  discrets  permettent  de  simuler des  systèmes  matériels
     14constitués  d'un ensemble  de composants  matériels  interconnectés par  des signaux.  Les
     15signaux véhiculent  fondamentatement deux tensions VSS et  VDD représentant respectivement
     16les valeurs Booléennes 0 et 1, mais  le simulateur doit traiter plus de valeurs pour gérer
     17les cas  spéciaux comme les signaux en  haute impédance, les conflits  électriques, ou les
     18valeurs  indéfinies.  A  titre indicatif,  le  VHDL standard  utilise 9  valeurs pour  les
     19signaux.  Pour simplifier, nous nous limiterons  dans ce TME aux trois valeurs logiques 0,
     201, et U (indéfini).
     21
     22Dans le cas général, Chaque composant est modélisé par un processus, et tous les processus
     23s'exécutent en parallèle.   Chaque processus utilise les valeurs  de ses signaux d'entrées
     24pour calculer les valeurs  de ses signaux de sortie.  Un signal  possède un seul émetteur,
     25mais  peut  avoir  plusieurs  destinataires.    On  définit,  pour  chaque  processus,  un
     26sous-ensemble  des  signaux  d'entrée, appelé  liste  de  sensibilité  du processus  :  un
     27changement de valeur sur un signal appartenant à la liste de sensibilité peut entraîner un
     28changement de  valeur sur un signal  de sortie du  processus. Un processus doit  donc être
     29évalué à chaque fois que l'un des signaux de la liste de sensibilité change de valeur. Par
     30exemple,  la liste  de sensibilité  d'un processus  représentant un  automate de  Moore ne
     31comporte qu'un seul signal, qui est le signal d'horloge.
     32
     33On  s'intéresse  dans  ce TME  au  cas  particulier  des  réseaux Booléens:  un  processus
     34correspond à une expression Booléenne multi-niveaux. Dans ce cas particulier, un processus
     35possède  donc un  seul signal  de sortie,  et la  liste de  sensibilité contient  tous les
     36signaux d'entrée. Cette liste de sensibilité  est tout simplement le support de l'EBM, tel
     37que vu au  TME3. Le réseau Booléen  peut être représenté par un  graphe biparti comportant
     38deux types de noeuds:  des processus et des signaux. Les noeuds  à la périphérie du réseau
     39sont toujours des signaux.
     40
     41[[Image(reseau_booleen.png, nolink)]]
     42
     43En  d'autres termes,  un processus  a toujours  au moins  un signal  entrant et  un signal
     44sortant.   Les signaux  qui n'ont  pas  d'arrête entrante  sont les  entrées primaires  du
     45réseau, les signaux qui n'ont pas  d'arrête sortante sont les sorties primaires du réseau.
     46Les autres signaux sont appelés signaux internes.
     47
     48On  appelle événement  le  changement  de valeur  d'un  signal à  un  certain instant.  Un
     49événement est donc défini par un triplet (signal, date, valeur).
     50
     51Simuler le  fonctionnement d'un  circuit consiste  donc à calculer  pour chaque  signal la
     52succession des  événements, appelée forme d'onde.   L'ensemble des formes  d'ondes de tous
     53les signaux constitue un chronogramme.
     54
     55La simulation suppose que l'on possède  une fonction d'évaluation qui calcule la valeur du
     56signal de sortie  du processus en fonction  de la valeur des signaux  d'entrée. Dans notre
     57cas, nous utiliserons  la méthode {{{Ebm::eval()}}} qui gère les  trois valeurs de signaux
     58(0,1,U).
     59
     60Créez un  répertoire de travail TME5,  et copiez dans  ce répertoire les  fichiers qui se
     61trouvent dans {{{/users/enseig/jpc/M1-CAO/TME/5.public}}}.
     62
     63
     64= A) Structures de données =
     65
     66On utilise deux structures de données pour représenter :
     67
     68 * Le réseau Booléen ({{{BoolNet}}}), c'est à  dire le graphe biparti des processus et des
     69   signaux.
     70
     71 * L'échéancier ({{{Scheduler}}}), c'est à dire l'ensemble ordonné des événements.
     72
     73
     74== A1) réseau Booléen ==
     75
     76Un réseau  booléen est la  représentation d'un graphe  bipartie. Il est donc  constitué de
     77deux types de noeuds et d'arcs orientés reliants les noeuds entre eux.
     78
     79Les deux types de noeuds sont les ''signaux'' et les ''processus''.
     80
     81Un arc  orienté relie  un noeud source  à un noeud  cible. Notez  que comme le  graphe est
     82bipartie, les noeuds  sources et destination sont toujours de types  différents.  On ne va
     83pas créer d'objet spécifique pour représenter  un arc. Plus simplement, les noeuds sources
     84contiendront une liste de noeuds cible.
     85
     86 * Un ''Signal'' considéré  comme source, peut être utilisé dans  un nombre quelquonque de
     87   processus.  il aura donc une liste de ''Processus'' cibles.
     88
     89 * Un ''processus''  considéré comme source aura  une unique cible: le  ''signal'' dont il
     90   calcule la valeur. Il n'est donc pas  nécessaire de gérér une liste, un simple pointeur
     91   suffira. Notez  qu'il s'agit  d'une conséquence de  notre simplification du  modèle qui
     92   n'est pas applicable dans le cas général.
     93
     94'''La classe {{{BoolNet}}}'''
     95
     96En plus des accesseurs triviaux, elle fourni une fonction de recherche d'un signal par son
     97nom {{{getSignal(const std::string&  )}}} ainsi que deux méthodes  pour la construction du
     98réseau booléen. Le réseau booléen est initialisé vide.
     99
     100 * Méthode {{{addSignal()}}}: ajoute un nouveau noeud  de type ''signal''. On donne le nom
     101  du signal  ainsi que son type  (parmis {{{In}}}, {{{Out}}} et  {{{Internal}}}.  La liste
     102  des cibles du noeud est initialisée vide.
     103 * Méthode {{{addProcess()}}}:  ajoute un nouveau  noeud de type ''processus''.   On donne
     104   respectivement  comme arguments,  le nom  du ''signal''  cible,  l'expression booléenne
     105   qu'il représente (sous forme textuelle) et le delai de calcul de ce processus.
     106
     107'''Importante  remarque''':  la représentation  des  arcs  (listes  de cibles  des  noeuds
     108sources) sont  construites lors de la création  des processus.  Il est  donc impératif que
     109tous les ''signaux'' soient crées avant les ''processus''.
     110
     111{{{
     112#!cpp
     113class BoolNet {
     114  private:
     115    std::string            _name;
     116    std::vector<Signal*>   _signals;
     117    std::vector<Process*>  _processes;
     118  public:
     119                                  BoolNet      ( const std::string& );
     120    inline std::string&           getName      ();
     121           Signal*                getSignal    ( const std::string& );
     122    inline std::vector<Signal*>&  getSignals   ();
     123    inline std::vector<Process*>& getProcesses ();
     124           Signal*                addSignal    ( const std::string&, SignalType );
     125           Process*               addProcess   ( const std::string&, const std::string&, unsigned int delay );
     126};
     127}}}
     128
     129'''La classe {{{Signal}}}'''
     130
     131Attributs:
     132
     133 * {{{_network}}} : le réseau booléen auquel elle appartient.
     134 * {{{_variable}}} :  l'{{{EbmVar}}} qu'elle encapsule.  Le contructeur devra  créer cette
     135   {{{EbmVar}}} à la contruction.
     136 * {{{_type}}} : le type du signal (parmis {{{In}}}, {{{Out}}} et {{{Internal}}}).
     137 * {{{_processes}}}  : la représentation  des arcs.  On choisi  un {{{set<>}}}  pour gérer
     138   automatiquement l'unicité.
     139
     140Méthode non triviale:
     141
     142 * {{{addProcess()}}}  : ajoute une  nouvelle cible  à l'ensemble  des cibles.  Equivaut à
     143   créer un arc dans le graphe.
     144
     145{{{
     146#!cpp
     147class Signal {
     148  private:
     149    BoolNet*            _network;
     150    EbmVar*             _variable;
     151    SignalType          _type;
     152    std::set<Process*>  _processes;
     153  public:
     154                               Signal       ( BoolNet*, const std::string&, SignalType );
     155    inline std::string         getName      ();
     156    inline SignalType          getType      ();
     157    inline ValueType           getValue     ();
     158    inline std::set<Process*>& getProcesses ();
     159    inline void                addProcess   ( Process* );
     160    inline void                setValue     ( ValueType );
     161};
     162}}}
     163
     164'''La classe {{{Process}}}'''
     165
     166Attributs:
     167
     168 * {{{_network}}} : le réseau booléen auquel elle appartient.
     169 * {{{_signal}}} : le signal qu'elle calcule. C'est la représentation de l'unique arc issu
     170   d'un noeud de type ''processus''.
     171 * {{{_expression}}} :  l'{{{EbmExpr}}} de  calcul. On  la créera à  l'aide de  la méthode
     172   {{{Ebm::parse()}}} qui vous est fournie.
     173 * {{{_delay}}} : le temps nécessaire au calcul de la nouvelle valeur. Représente un temps
     174   de propagation au travers des portes logiques.
     175
     176Méthodes non triviales:
     177
     178 * {{{Process()}}} : le  constructeur en plus de sa tâche  d'initialisation des membres de
     179   l'objet  devra créer  les  ''arcs'' entre  les  ''signaux'' appartenant  au support  de
     180   l'expression et le processus courant.
     181 * {{{eval()}}}  et {{{display()}}}  sont des  encapsulations des  méthodes  identiques de
     182   l'objet {{{Ebm}}}.
     183
     184{{{
     185#!cpp
     186class Process {
     187  private:
     188    BoolNet*      _network;
     189    Signal*       _signal;
     190    Ebm*          _expression;
     191    unsigned int  _delay;
     192  public:
     193                         Process       ( BoolNet*, Signal*, const std::string& expression, unsigned int delay=0 );
     194           ValueType     eval          ();
     195    inline Ebm*          getExpression ();
     196    inline Signal*       getSignal     ();
     197    inline unsigned int  getDelay      ();
     198           void          display       ( std::ostream& );
     199};
     200}}}
     201
     202
     203== A2) échéancier ==
     204
     205Le rôle  de l'échéancier  est d'enregistrer  et d'ordonner les  évènements dans  le temps.
     206Pour le réaliser nous avons besoin des éléments suivants:
     207
     208 * Une date (classe {{{Time}}}) contenant le temps écoulé depuis le début de la simulation
     209   en nano-secondes  '''et''' un delta-cycle. Le delta-cycle  permettant d'avoir plusieurs
     210   simulations ''au  même temps physique'' mais  cependant séparés pour ne  pas générer de
     211   problèmes de causalité.
     212
     213 * Un événement (classe {{{Event}}}), avec le  temps ({{{Time}}}) auquel il se produit, Le
     214   ''signal'' qu'il affecte et la nouvelle valeur que va prendre ce signal.
     215
     216 * Une structure  lui permettant  de stocker  les ensembles d'évenements  par dates  et de
     217   trier  ces  ensembles par  date  (croissantes).  Nous  allons  pour  cela utiliser  une
     218   {{{map<>}}} de {{{vector<>}}}.  C'est à  dire, une {{{map<>}}} dont chaque élément sera
     219   un {{{vector<>}}}  d'évènements et la clé  une date ({{{Time}}}. Notez  qu'au sein d'un
     220   ensemble d'évènements synchrones l'ordre est indifférent.
     221
     222'''Propriérés remarquables de la {{{map<>}}} de {{{vector<>}}}'''
     223
     224'''Syntaxe:'''
     225{{{
     226#!cpp
     227map<Time, vector<Event*> >  _events;
     228}}}
     229
     230La  clé de tri  est un  objet de  type {{{Time}}}  et la  valeur associée  à cette  clé un
     231{{{vector<Event*>}}}.  Il  faudra définir  pour  la  classe  {{{Time}}} une  surcharge  de
     232l'operateur   ''strictement   inférieur''   ({{{operator<()}}})   qui   définira   l'ordre
     233chronologique.
     234
     235'''Accès et itérateurs'''
     236{{{
     237#!cpp
     238map<Time, vector<Event*> >            _events;
     239map<Time, vector<Event*> >::iterator  istate = _events.begin();
     240
     241for ( ; istate != _events.end() ; ++istate ) {
     242  const Time&     date   = (*istate).first;
     243  vector<Event*>& events = (*istate).second;
     244
     245// Do something here.
     246}
     247}}}
     248
     249 * '''Ordre du parcours''': une propriété fondamentale est que lors d'un parcours de la
     250   {{{map<>}}} avec des itérateurs, les éléments sont parcourus ''dans l'ordre défini
     251   par la relation d'ordre de la clé'', c'est à dire dans notre cas, l'ordre chronologique
     252   de {{{Time}}}.
     253
     254 * Cet ordre est maintenu automatiquent lors d'ajout ou de retrait d'élements dans la
     255   {{{map<>}}}.
     256
     257 * Les itérateurs pointant sur des éléments de la {{{map<>}}} restent valides même si l'on
     258   ajoute ou retire des éléments (une seule exception: si l'on retire l'élement sur lequel
     259   l'itérateur pointe...).
     260
     261 * Les éléments d'une {{{map<>}}} sont des paires {{{(clé,valeur)}}}, pour y accéder à
     262   partir de l'itérateur, il faut utiliser les attributs public {{{first}}} (clé) et
     263   {{{second}}} (valeur).
     264
     265
     266'''La classe {{{Time}}}'''
     267
     268{{{
     269#!cpp
     270// Time is used as key by the scheduler's map<>.
     271class Time {
     272  private:
     273    unsigned int  _time;
     274    unsigned int  _deltaCycle;
     275  public:
     276    inline               Time          ( unsigned int time, unsigned int dc );
     277    inline unsigned int  getTime       () const;
     278    inline unsigned int  getDeltaCycle () const;
     279    friend bool          operator<     ( const Time& lhs, const Time& rhs );
     280};
     281}}}
     282
     283
     284'''La classe {{{Event}}}'''
     285
     286L'attribut  {{{_value}}}   contient  la  ''prochaine''   valeur  du  signal.   La  méthode
     287{{{Event::updateSignalValue()}}} est chargé de faire la mise à jour effective de la valeur
     288du signal. Elle sera appelée dans l'étape de mise à jour du simulateur.
     289
     290{{{
     291#!cpp
     292class Event {
     293  private:
     294    Signal*    _signal;
     295    ValueType  _value;
     296    Time       _time;
     297  public:
     298    inline             Event             ( Signal*, ValueType, Time& );
     299    inline  Signal*    getSignal         ();
     300    inline  ValueType  getValue          ();
     301    inline  Time&      getTime           ();
     302    inline  void       updateSignalValue ();
     303};
     304}}}
     305
     306
     307'''La classe {{{Scheduler}}}'''
     308
     309Méthodes non triviales:
     310
     311 * {{{addEvent(Signal*,...)}}} : ajoute un nouvel évènement à l'échéancier. Pour donner la
     312   date  on ne  passe  pas d'objet  {{{Time}}},  mais ses  deux  composants {{{time}}}  et
     313   {{{dc}}}.
     314
     315 * {{{addEvent(const std::string&,...)}}}  : une surcharge  de la fonction  précédente qui
     316   prend comme premier  argument un nom de ''signal'' au lieu  du pointeur sur ''signal''.
     317   Cette méthode sera utilisée préférentiellement pour initialiser l'échéancier.
     318
     319 * {{{simulate()}}} : effectue la simulation.
     320
     321 * {{{drive()}}} : écrit dans le flot donné  en argument le résultat de la simulation dans
     322   un format  lisible par  l'outil {{{xpat}}}.  La définition de  cette fonction  vous est
     323   fournie.
     324
     325 * {{{_reset()}}}  : remet  toutes les  variables  (entrées, sorties,  internes) à  l'état
     326   initial (U).
     327
     328 * {{{_header()}}} et {{{_display()}}} :  utilitaires vous permettant d'afficher l'état de
     329   la simulation à un instant donné.
     330
     331{{{
     332#!cpp
     333class Scheduler {
     334  private:
     335    BoolNet*                              _network;
     336    std::map<Time, std::vector<Event*> >  _events;
     337  public:
     338           Scheduler ( BoolNet* );
     339    Event* addEvent  ( const std::string& variable, ValueType, unsigned int time, unsigned int dc=0 );
     340    Event* addEvent  ( Signal*, ValueType, unsigned int time, unsigned int dc=0 );
     341    void   simulate  ();
     342    void   drive     ( std::ostream& );
     343  private:
     344    void   _reset    ();
     345    void   _header   ();
     346    void   _display  ( const Time& );
     347};
     348}}}
     349
     350
     351= C) Travail à réaliser =
     352
     353Dans un premier temps vous devrez utiliser le simulateur qui vous est fourni dans
     354l'ensemble des fichiers {{{.o}}}.
     355
     356Dans un second temps, il vous est demandé de progressivment remplacer les {{{.o}}}
     357fournis par les vôtres.
     358
     359
     360== C1) simulation du circuit ''etou'' ==
     361
     362Le fichier ''etou.c'' contient un tout petit réseau Booléen ne contenant que deux noeuds,
     363et 4 signaux. Compilez ce fihier, et exécutez la simulation.
     364
     365Vous pouvez visualiser le réseau Booléen avec la commande:
     366{{{
     367> eog etou.gif
     368}}}
     369Vous pouvez visualiser le chronogramme résulat de la simulation avec la commande:
     370{{{
     371> xpat -l etou
     372}}}
     373
     374
     375== C2) Simulation d'un additionneur 2 bits ==
     376
     377On se propose de simuler le schéma suivant, qui réalise un additionneur 2 bits.
     378A chaque porte est associé une expression Booléenne représentée par un EBM.
     379
     380[[Image(schema_portes.png, nolink)]]
     381
     382
     383== C2.1) Construction réseau Booléen de l'additionneur ==
     384
     385En vous inspirant du fichier  {{{etou.c}}}, écrivez le fichier {{{adder.c}}} qui construit
     386en  mémoire  le  réseau  Booléen  correspondant  au circuit  additionneur  2  bits  décrit
     387ci-dessus.   On   utilisera  pour   cela  les  fonctions   {{{BoolNet::addProcess()}}}  et
     388{{{BoolNet::addSignal()}}}.  On prendra une valeur de 1 ns pour le temps de propagation de
     389la porte {{{NAND2}}}, et de 2 ns  pour la porte {{{XOR2}}}.  Pour vérifier la structure du
     390réseau  Booléen, on  utilisera la  fonction {{{toDot()}}}.   Cette fonction  construit une
     391représentation graphique  du réseau Booléen,  et la sauvegarde  dans un fichier  au format
     392{{{.gif}}} ou {{{.ps}}}.
     393
     394Modifiez le Makefile permettant de compiler ce programme ''adder.c'', et exécutez-le.
     395
     396
     397== C2.2) Construction et initialisation de l’échéancier ==
     398
     399Compléter le fichier {{{adder.c}}} {{{main()}}} pour créer l’échéancier et initialiser les
     400événements  sur les  signaux d’  entrée a0,  b0,  c0, a1  et b1  de façon  à respecter  le
     401chronogramme ci-dessous.   On utilisera la fonction  Scheduler::addEvent() et add_event().
     402Attention  : le  passage de  la valeur  U (indéfinie)  à une  valeur 0  ou 1  constitue un
     403événement : Dans ce  chronogramme, il y a donc un événement  sur tous les signaux d’entrée
     404au temps {{{T = 0}}}.
     405
     406[[Image(chronogramme.png, nolink)]]
     407
     408Pour vérifier que l’échéancier est correctement initialisé, on pourra utiliser la fonction
     409''Scheduler::drive()''.  Cette  fonction peut être  utilisée avant même l'exécution  de la
     410fonction de simulation, pour générer un  fichier au format .pat qui décrit le chronogramme
     411des signaux d'entrée. Vous pouvez visualiser ce chronogramme avec l’outil {{{xpat}}}.
     412
     413
     414== C2.3) Simulation effective du circuit additionneur ==
     415
     416Introduisez  dans  le fichier  {{{adder.c}}}  la  méthode {{{Scheduler::simulate()}}}  qui
     417effectue la simulation du réseau Booléen, jusqu'à  ce qu'il n'y ait plus aucun événement à
     418traiter dans l'échéancier. Compilez ce programme, et analysez le chronogramme résultant
     419
     420
     421== C2.4) Ecriture de la boucle de simulation ==
     422
     423Implémenter et remplacer progressivement toutes les classes qui vous on été fournies.