| 1 | {{{ |
| 2 | #!html |
| 3 | <h1>TME 7 : Simulation logico-temporelle</h1> |
| 4 | }}} |
| 5 | [[PageOutline]] |
| 6 | |
| 7 | = Objectif = |
| 8 | |
| 9 | On souhaite réaliser dans ce TME un petit simulateur logico-temporel, permettant de |
| 10 | simuler un réseau Booléen temporisé, où les expressions Booléennes sont représentées par |
| 11 | des arbres EBM (voir TME3). |
| 12 | |
| 13 | Les simulateurs à événements discrets permettent de simuler des systèmes matériels |
| 14 | constitués d'un ensemble de composants matériels interconnectés par des signaux. Dans |
| 15 | notre cas, les signaux véhiculent fondamentatement deux tensions VSS et VDD représentant |
| 16 | respectivement les valeurs Booléennes 0 et 1, mais le simulateur doit traiter plus de |
| 17 | valeurs 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 |
| 19 | valeurs pour les signaux. Pour simplifier, nous nous limiterons dans ce TME aux trois |
| 20 | valeurs logiques 0, 1, et U (Undefined). |
| 21 | |
| 22 | Dans le cas général, chaque composant est modélisé par un processus, et tous les processus |
| 23 | s'exécutent en parallèle. Chaque processus utilise les valeurs de ses signaux d'entrées |
| 24 | pour calculer les valeurs de ses signaux de sortie. Un signal possède un seul émetteur, |
| 25 | mais peut avoir plusieurs destinataires. On définit, pour chaque processus, un |
| 26 | sous-ensemble des signaux d'entrée, appelé liste de sensibilité du processus : un |
| 27 | changement de valeur sur un signal appartenant à la liste de sensibilité peut entraîner un |
| 28 | changement 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 |
| 30 | exemple, la liste de sensibilité d'un processus représentant un automate de Moore ne |
| 31 | comporte qu'un seul signal, qui est le signal d'horloge. |
| 32 | |
| 33 | On s'intéresse dans ce TME au cas particulier des réseaux Booléens: un processus |
| 34 | correspond à une expression Booléenne multi-niveaux. Dans ce cas particulier, un processus |
| 35 | possède donc un seul signal de sortie, et la liste de sensibilité du processus contient |
| 36 | tous les signaux d'entrée. Cette liste de sensibilité est tout simplement le support de |
| 37 | l'EBM, tel que vu au TME3. Un réseau Booléen peut être représenté par un graphe biparti |
| 38 | orienté comportant deux types de noeuds: des '''processus''' et des '''signaux'''. Les |
| 39 | noeuds à la périphérie du réseau sont toujours des signaux. |
| 40 | |
| 41 | En d'autres termes, un processus a toujours au moins un signal entrant et un signal |
| 42 | sortant. Les signaux qui n'ont pas d'arc entrant sont les entrées primaires du réseau, |
| 43 | les signaux qui n'ont pas d'arc sortant sont les sorties primaires du réseau. Les autres |
| 44 | signaux sont appelés signaux internes. |
| 45 | |
| 46 | [[Image(reseau_booleen.png, nolink)]] |
| 47 | |
| 48 | On 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 | |
| 51 | Simuler le fonctionnement d'un circuit consiste donc à calculer pour chaque signal la |
| 52 | succession des événements, appelée forme d'onde. L'ensemble des formes d'ondes de tous |
| 53 | les signaux constitue un chronogramme. |
| 54 | |
| 55 | La simulation suppose que l'on possède une fonction d'évaluation qui calcule la valeur du |
| 56 | signal de sortie du processus en fonction de la valeur des signaux d'entrée. Dans notre |
| 57 | cas, nous utiliserons la méthode {{{Ebm::eval()}}} qui gère les trois valeurs de signaux |
| 58 | (0,1,U). |
| 59 | |
| 60 | Créez un répertoire de travail TME5, et copiez dans ce répertoire les fichiers qui se |
| 61 | trouvent dans {{{/users/enseig/jpc/M1-CAO/TME/5.public}}}. Vous pouvez aussi récupérer |
| 62 | l'archive suivante: [attachment:TME5-public.tar.bz2]. |
| 63 | |
| 64 | |
| 65 | = A) Structures de données = |
| 66 | |
| 67 | On 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 | |
| 77 | Un réseau booléen est représenté par un graphe orienté biparti. Il est donc constitué de |
| 78 | deux types de noeuds et d'arcs orientés reliants les noeuds entre eux. |
| 79 | |
| 80 | Les deux types de noeuds sont les ''signaux'' et les ''processus''. |
| 81 | |
| 82 | Un arc orienté relie un noeud source à un noeud cible. Notez que comme le graphe est |
| 83 | biparti, les noeuds sources et destination sont toujours de types différents. On ne va |
| 84 | pas créer d'objet spécifique pour représenter un arc. Plus simplement, les noeuds sources |
| 85 | contiendront 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 | |
| 96 | Elle contient un ensemble de signaux et un ensemble de processus. |
| 97 | |
| 98 | En plus des accesseurs triviaux, elle fourni une fonction de recherche d'un signal par son |
| 99 | nom {{{getSignal(const std::string& )}}} ainsi que deux méthodes pour la construction du |
| 100 | ré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 |
| 113 | construits 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 | |
| 116 | La méthode {{{toDot()}}} crée une représentation graphique du réseau booléen. Cette |
| 117 | fonction vous est fournie. |
| 118 | |
| 119 | {{{ |
| 120 | #!cpp |
| 121 | class 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 | |
| 141 | Attributs: |
| 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 | |
| 150 | Mé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 |
| 158 | class 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 | |
| 177 | Attributs: |
| 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 | |
| 187 | Mé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 |
| 197 | class 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 | |
| 216 | Le 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 |
| 218 | suivants: |
| 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 |
| 242 | map<Date, vector<Event*> > _events; |
| 243 | }}} |
| 244 | |
| 245 | La 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 |
| 247 | l'operateur ''strictement inférieur'' ({{{operator<()}}}) qui définira l'ordre |
| 248 | chronologique. |
| 249 | |
| 250 | '''Accès et itérateurs''' |
| 251 | {{{ |
| 252 | #!cpp |
| 253 | map<Date, vector<Event*> > _events; |
| 254 | map<Date, vector<Event*> >::iterator istate = _events.begin(); |
| 255 | |
| 256 | for ( ; 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 |
| 283 | class 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 | |
| 298 | L'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 |
| 300 | du signal. Elle sera appelée dans l'étape de mise à jour du simulateur. |
| 301 | |
| 302 | {{{ |
| 303 | #!cpp |
| 304 | class 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 | |
| 321 | Mé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 |
| 345 | class 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 | |
| 366 | Dans un premier temps vous devrez utiliser le simulateur qui vous est fourni dans |
| 367 | l'ensemble des fichiers {{{.o}}}. |
| 368 | |
| 369 | Dans 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 | |
| 375 | Le fichier {{{AndOr.c}}} contient un tout petit réseau Booléen ne contenant que deux |
| 376 | noeuds, et 4 signaux. Compilez ce fihier, et exécutez la simulation. |
| 377 | |
| 378 | Vous pouvez visualiser le réseau Booléen avec la commande: |
| 379 | {{{ |
| 380 | > eog AndOr.png |
| 381 | }}} |
| 382 | Vous 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 | |
| 390 | On se propose de simuler le schéma suivant, qui réalise un additionneur 2 bits. |
| 391 | A 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 | |
| 398 | En vous inspirant du fichier {{{AndOr.c}}}, écrivez le fichier {{{Adder.c}}} qui construit |
| 399 | en mémoire le réseau Booléen correspondant au circuit additionneur 2 bits décrit |
| 400 | ci-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 |
| 402 | la porte {{{NAND2}}}, et de 2 ns pour la porte {{{XOR2}}}. Pour vérifier la structure du |
| 403 | réseau Booléen, on utilisera la fonction {{{toDot()}}}. Cette fonction construit une |
| 404 | représentation graphique du réseau Booléen, et la sauvegarde dans un fichier au format |
| 405 | {{{.gif}}} ou {{{.ps}}}. |
| 406 | |
| 407 | Modifiez 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 | |
| 412 | Complé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 |
| 414 | chronogramme ci-dessous. On utilisera la fonction Scheduler::addEvent() et add_event(). |
| 415 | Attention : 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 |
| 417 | au temps {{{T = 0}}}. |
| 418 | |
| 419 | [[Image(chronogramme.png,nolink)]] |
| 420 | |
| 421 | Pour 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 |
| 423 | fonction de simulation, pour générer un fichier au format .pat qui décrit le chronogramme |
| 424 | des signaux d'entrée. Vous pouvez visualiser ce chronogramme avec l’outil {{{xpat}}}. |
| 425 | |
| 426 | == C2.3) Simulation effective du circuit additionneur == |
| 427 | |
| 428 | Introduisez dans le fichier {{{Adder.c}}} la méth |