Changes between Initial Version and Version 1 of 2011CaoTme9


Ignore:
Timestamp:
May 6, 2011, 12:21:41 AM (13 years ago)
Author:
jpc
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • 2011CaoTme9

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