| 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 simuler |
| 10 | un réseau Booléen temporisé, où les expressions Booléennes sont représentées par des arbres ABL (voir TME5). |
| 11 | |
| 12 | Les simulateurs à événements discrets permettent de simuler des systèmes matériels constitués d'un ensemble |
| 13 | de composants matériels interconnectés par des signaux. Les signaux véhiculent fondamentatement |
| 14 | deux tensions VSS et VDD représentant respectivement les valeurs Booléennes 0 et 1, mais le simulateur doit traiter |
| 15 | plus de valeurs pour gérer les cas spéciaux comme les signaux en haute impédance, les conflits électriques, ou les |
| 16 | valeurs indéfinies. A titre indicatif, le VHDL standard utilise 9 valeurs pour les signaux. |
| 17 | Pour simplifier, nous nous limiterons dans ce TME aux trois valeurs logiques 0, 1, et U (indéfini). |
| 18 | |
| 19 | Dans le cas général, Chaque composant est modélisé par un processus, et tous les processus s'exécutent en parallèle. |
| 20 | Chaque processus utilise les valeurs de ses signaux d'entrées pour calculer les valeurs de ses signaux de sortie. |
| 21 | Un signal possède un seul émetteur, mais peut avoir plusieurs destinataires. |
| 22 | On définit, pour chaque processus, un sous-ensemble des signaux d'entrée, appelé liste de sensibilité du processus : |
| 23 | un changement de valeur sur un signal appartenant à la liste de sensibilité peut entraîner un changement de valeur |
| 24 | sur un signal de sortie du processus. Un processus doit donc être évalué à chaque fois que l'un des signaux de la liste |
| 25 | de sensibilité change de valeur. Par exemple, la liste de sensibilité d'un processus représentant un automate de Moore |
| 26 | ne comporte qu'un seul signal, qui est le signal d'horloge. |
| 27 | |
| 28 | On s'intéresse dans ce TME au cas particulier des réseaux Booléens: un processus correspond à une expression Booléenne |
| 29 | multi-niveaux, représentée par un arbre ABL. Dans ce cas particulier, un processus possède donc un seul signal de sortie, |
| 30 | et la liste de sensibilité contient tous les signaux d'entrée. Le réseau Booléen peut être représenté par un graphe biparti |
| 31 | comportant deux types de noeuds : des processus et des signaux. Les noeuds à la périphérie du réseau sont toujours des signaux. |
| 32 | |
| 33 | [[Image(reseau_booleen.png, nolink)]] |
| 34 | |
| 35 | En d'autres termes, un processus a toujours au moins un signal entrant et un signal sortant. Les signaux qui n'ont pas d'arrête |
| 36 | entrante sont les entrées primaires du réseau, les signaux qui n'ont pas d'arrête sortante sont les sorties primaires du réseau. |
| 37 | Les autres signaux sont appelés signaux internes. |
| 38 | |
| 39 | On appelle événement le changement de valeur d'un signal à un certain instant. Un événement est donc défini par un triplet (signal, date, valeur). |
| 40 | |
| 41 | Simuler le fonctionnement d'un circuit consiste donc |
| 42 | à calculer pour chaque signal la succession des événements, appelée forme d'onde. |
| 43 | L'ensemble des formes d'ondes de tous les signaux constitue un chronogramme. |
| 44 | |
| 45 | La simulation suppose que l'on possède une fonction d'évaluation qui calcule la valeur du signal de sortie du processus en fonction |
| 46 | de la valeur des signaux d'entrée. Dans notre cas, il faudra disposer d’une fonction eval_abl() capable de traiter les trois valeurs (0,1,U). |
| 47 | |
| 48 | Créez un répertoire de travail TME7, et copiez dans ce répertoire tous les fichiers et répertoires utiles pour ce TME. |
| 49 | {{{ |
| 50 | cp -rp /users/enseig/encadr/cao/tme7 . |
| 51 | }}} |
| 52 | |
| 53 | = A) Structures de données = |
| 54 | |
| 55 | On utilise deux structures de données pour représenter : |
| 56 | * le réseau Booléen, c'est à dire le graphe biparti des processus et des signaux, |
| 57 | * l'échéancier, c'est à dire l'ensemble ordonné des événements. |
| 58 | |
| 59 | '''A1) réseau Booléen''' |
| 60 | |
| 61 | Le réseau Booléen est constitué d'un ensemble de signaux et d'un ensemble de processus. |
| 62 | Les signaux sont regroupés dans trois sous-ensembles disjoints suivant leur type (IN, OUT, INTERNAL). |
| 63 | {{{ |
| 64 | typedef struct boolnet_t { |
| 65 | char * NAME; // nom du circuit modélisé |
| 66 | signal_t * IN ; // ensemble des signaux IN |
| 67 | signal_t * OUT ; // ensemble des signaux OUT |
| 68 | signal_t * INTERNAL; // ensemble des signaux INTERNAL |
| 69 | process_t * PROCESS; // ensemble des proceessus |
| 70 | } boolnet_t; |
| 71 | }}} |
| 72 | L’ordre dans les listes des signaux et des processus n'est pas significatif. Ils sont chaînés dans l'ordre de leur création. |
| 73 | |
| 74 | Un signal possède un type, un pointeur sur une variable Booleenne (var_t), qui définit son nom et sa valeur (0,1 ou U). |
| 75 | Il possède également un pointeur sur le processus dont il est la sortie, et un pointeur sur la liste chaînée des processus dont il est une entrée. |
| 76 | {{{ |
| 77 | typedef struct signal_t { |
| 78 | boolnet_t * BOOLNET; // pointeur sur le réseau Booléen |
| 79 | sigtype_t TYPE; // type du signal |
| 80 | var_t * VAR; // variable Booléenne associée |
| 81 | bip_t * TO_PROCESS; // liste des process destinataires |
| 82 | process_t * FROM_PROCESS; // process source |
| 83 | signal_t * NEXT; // signal suivant de même type |
| 84 | } signal_t; |
| 85 | }}} |
| 86 | Le type pourra prendre une des valeurs du type enuméré suivant : |
| 87 | {{{ |
| 88 | enum sigtype_t { |
| 89 | IN = 0, |
| 90 | OUT = 1, |
| 91 | INTERNAL = 2}; |
| 92 | }}} |
| 93 | Un processus est une expression Booléenne qui définit la valeur du signal de sortie, en fonction des valeurs des signaux d’entrée. |
| 94 | Il est caractérisé par un temps de propagation qui est le retard entre un événement sur une entrée et l'événement sur la sortie |
| 95 | qui en est la conséquence. Dans le cas général, les temps de propagation dépendent de l'entrée qui commute, |
| 96 | mais pour simplifier le simulateur, on suppose que le temps de propagation ne dépend pas de l'entrée qui commute. |
| 97 | {{{ |
| 98 | typedef struct process_t { |
| 99 | boolnet_t * BOOLNET; // pointeur sur le réseau Booléen |
| 100 | signal_t * SIGNAL; // pointeur sur le signal de sortie |
| 101 | bip_t * ABL; // pointeur sur l’arbre ABL associé |
| 102 | long DELAY; // retard entrée -> sortie (en ps) |
| 103 | bip_t * SUPPORT; // liste des signaux d’entrée |
| 104 | process_t * NEXT; // processus suivant |
| 105 | } process_t |
| 106 | }}} |
| 107 | |
| 108 | '''A2) échéancier''' |
| 109 | |
| 110 | L'échéancier permet d'enregistrer et d'ordonner les événements dans le temps. |
| 111 | Ceux-ci sont donc rangés dans une liste doublement chaînée, par dates croissantes. |
| 112 | Un événement représente une affection de valeur à un signal à une certaine date. |
| 113 | {{{ |
| 114 | #!c |
| 115 | typedef struct event_t { |
| 116 | signal_t * SIGNAL; // signal modifié |
| 117 | unsigned VALUE; // nouvelle valeur |
| 118 | long DATE; // date de la modification |
| 119 | event_t * PREV ; // événement précédent dans l’echéancier |
| 120 | event_t * NEXT; // événement suivant dans l’echéancier |
| 121 | } event_t; |
| 122 | }}} |
| 123 | L'échéancier peut contenir plusieurs événements à la même DATE, mais portant sur des signaux différents. |
| 124 | Ces événements synchrones sont rangés consécutivement dans la liste chaînée, |
| 125 | mais l'ordre entre ces événements synchrones n'est pas significatif. |
| 126 | L'échéancier représente la liste ordonnée des événements passés, présents, et futurs. |
| 127 | Il contient donc en particulier la variable TC qui représente la date courante. |
| 128 | {{{ |
| 129 | #!c |
| 130 | typedef struct scheduler_t { |
| 131 | long TC; // Temps Courant |
| 132 | event_t * CURRENT; // pointeur sur le premier événement à a date TC |
| 133 | event_t * FIRST; // pointeur sur le premier événement de l'échéancier |
| 134 | } scheduler_t; |
| 135 | }}} |
| 136 | |
| 137 | = B) Fonctions d'accès aux structures de données = |
| 138 | |
| 139 | On présente ici les différentes fonctions permettant d’accéder aux structures de données définies ci-dessus. |
| 140 | |
| 141 | Dans un premier temps le code binaire exécutable de ces fonction vous est fourni, |
| 142 | et vous pourrez utiliser ces fonctions pour construire en mémoire le réseau Booléen, |
| 143 | construire et initialiser l’échéancier, et écrire la boucle principale de simulation qui a été présentée en cours. |
| 144 | |
| 145 | Dans un deuxième temps, il vous sera demandé d’écrire vous-même le code de ces fonctions. |
| 146 | |
| 147 | '''B1) construction réseau Booléen''' |
| 148 | |
| 149 | {{{ |
| 150 | boolnet_t * cons_boolnet (char * name) |
| 151 | }}} |
| 152 | Cette fonction construit un réseau Booléen "vide" de nom ''name''. |
| 153 | Les pointeurs sur les listes de signaux et de processus sont initialisés à NULL. |
| 154 | {{{ |
| 155 | signal_t * cons_signal (boolnet_t * bn, sigtype_t type, var_t * var) |
| 156 | }}} |
| 157 | Cette fonction prend pour argument une variable Booléenne préalablement créée. |
| 158 | Elle crée un signal et l’insère dans la liste des signaux du réseau correspondant à son type. |
| 159 | Elle initialise à NULL les pointeurs FROM_PROCESS et TO_PROCESS. La valeur de la variable Booléenne est forcée à U. |
| 160 | {{{ |
| 161 | process_t * cons_process (boolnet_t * bn, signal_t * sig, char * abl, long delay) |
| 162 | }}} |
| 163 | Cette fonction prend pour argument un pointeur sur le signal de sortie, un pointeur sur un arbre ABL préalablement créé |
| 164 | (en utilisant par exemple la fonction parse_abl du TME5), et la valeur du temps de propagation. |
| 165 | Elle crée une structure process_t, calcule le support de l'abl (sous forme d’une liste de signaux d’entrée), |
| 166 | et met à jour les pointeurs contenus dans les structures représentant les signaux impliqués. |
| 167 | |
| 168 | '''Attention :''' La fonction support_abl du TME5 retourne une liste de variables, mais le champs SUPPORT de l'objet |
| 169 | process_t doit pointer sur une liste de signaux! |
| 170 | |
| 171 | Cette fonction doit vérifier le typage des signaux, c'est-à-dire qu'un signal de type IN ne peut avoir de processus générateur |
| 172 | et qu'un signal de type OUT ne peut apparaître dans le support d'une fonction. |
| 173 | |
| 174 | '''B2) construction échéancier''' |
| 175 | {{{ |
| 176 | scheduler_t * cons_scheduler() |
| 177 | }}} |
| 178 | Cette fonction fabrique un échéancier « vide ». |
| 179 | La date courante TC est initialisée à une valeur négative, et les pointeurs FIRST et CURRENT sont initialisés à NULL. |
| 180 | {{{ |
| 181 | event_t * cons_event(scheduler_t * sch, signal_t * sig, long date, unsigned val) |
| 182 | }}} |
| 183 | Cette fonction construit un événement sur le signal sig, à un certaine date, |
| 184 | avec la valeur val, et introduit cet événement dans l’échéancier. |
| 185 | Cette fonction met à jour les deux pointeurs FIRST et CURRENT dans l’échéancier, si cela est nécessaire. |
| 186 | '''Attention''' : Cette fonction ne doit être utilisée que dans la phase d’initialisation, pour introduire dans l’échéancier |
| 187 | les événements portant sur les signaux d’entrée du circuit (stimuli). |
| 188 | |
| 189 | '''B3) simulation''' |
| 190 | {{{ |
| 191 | void add_event (scheduler_t * sch, signal_t * sig, long delta, unsigned val) |
| 192 | }}} |
| 193 | Cette fonction construit un événement portant sur le signal sig, à la date TC + delta, avec la valeur val, et le range dans l'échéancier. |
| 194 | A la différence de la précédente, cette fonction utilise un temps relatif (delta), et ne modifie pas le les pointeurs FIRST et CURRENT. |
| 195 | C’est cette fonction qui doit être utilisée dans la boucle de simulation. |
| 196 | Dans le cas général, cette fonction doit en principe vérifier qu’il n’existe pas un autre événement postérieur sur le même signal, |
| 197 | et retirer cet événement parasite s’il y a lieu. Dans notre cas, cela n’est pas nécessaire. |
| 198 | Cette importante simplification est dûe à l'hypothèse qu'un processus est caractérisé par un unique temps de propagation, |
| 199 | qui ne dépend pas de l'entrée qui commute. Avec cette hypothèse, tous les événements prévus se produisent effectivement. |
| 200 | {{{ |
| 201 | bip_t * get_events (scheduler_t * sch) |
| 202 | }}} |
| 203 | Cette fonction recherche dans l'échéancier les événements prévus au temps TC. |
| 204 | Il peut y avoir plusieurs événements à la même date. Elle renvoie ces événements sous forme d'une liste chaînée de bi-pointeurs |
| 205 | (qu'il faut penser à libérer quand ils ne sont plus utiles). |
| 206 | {{{ |
| 207 | unsigned update_tc(scheduler_t * sch) |
| 208 | }}} |
| 209 | Cette fonction modifie la valeur de la variable TC en lui donnant la valeur de la date du premier événement strictement postérieur au temps courant. |
| 210 | Elle met à jour le pointeur CURRENT de l’échéancier. |
| 211 | Cette fonction renvoie la valeur 0 quand elle ne trouve pas d’événement postérieur au temps courant, |
| 212 | ce qui correspond à la fin de la simulation. Cette valeur doit donc être testée à chaque itération de la boucle de simulation. |
| 213 | {{{ |
| 214 | bip_t * update_signals ( bip_t * events) |
| 215 | }}} |
| 216 | Cette fonction prend pour argument un pointeur sur une liste d'événements (liste de bipointeurs). |
| 217 | Elle modifie la valeur des variables Booléennes associées à chacun des signaux pour lesquels il existe un événement. |
| 218 | Elle rend la liste des signaux qui ont été modifiés (une autre liste de bipointeurs, qu’il faut également libérer quand ils ne sont plus utilisés). |
| 219 | {{{ |
| 220 | bip_t * get_processes (boolnet_t * bn, bip_t * signals) |
| 221 | }}} |
| 222 | Cette fonction prend comme argument un pointeur sur le réseau Booléen, et une liste de signaux, |
| 223 | et elle renvoie un pointeur sur une autre liste contenant l’ensemble de tous les processus ayant |
| 224 | au moins un signal d’entrée appartenant à l’ensemble ''signals''. |
| 225 | (ici encore, il faut penser à libérer la mémoire occupée par les bi-pointeurs quand ils ne sont plus utiles). |
| 226 | {{{ |
| 227 | void eval_processes( scheduler_t * sch, bip_t * p) |
| 228 | }}} |
| 229 | Cette fonction prend pour argument une liste de processus, ainsi qu’un pointeur sur le scheduler, |
| 230 | et évalue la valeur du signal de sortie pour chacun des processus de la liste, en fonction des valeurs des signaux d’entrée. |
| 231 | Elle utilise pour cela une version de la fonction eval_abl() modifiée pour traiter une logique à trois valeurs (0,1,U). |
| 232 | Elle compare la valeur future du signal de sortie à sa valeur présente, et insère un événement dans l’échéancier |
| 233 | si cette valeur change (en utilisant la fonction add_event). |
| 234 | |
| 235 | = C) Travail à réaliser = |
| 236 | |
| 237 | On se propose de simuler le schéma suivant, qui réalise un additionneur 2 bits. |
| 238 | A chaque porte est associé une expression Booléenne représentée par un arbre ABL. |
| 239 | |
| 240 | [[Image(schema_portes.png, nolink)]] |
| 241 | |
| 242 | == C1) simulation du circuit ''etou'' == |
| 243 | |
| 244 | Le fichier ''etou.c'' contient un tout petit réseau Booléen ne contenant que deux noeuds, |
| 245 | et 4 signaux. Compilez ce fihier, et exécutez la simulation. |
| 246 | {{{ |
| 247 | > make |
| 248 | }}} |
| 249 | Lancez l'exécution duprogramme (construction du réseau Booléen et simulation). |
| 250 | {{{ |
| 251 | > ./etou |
| 252 | }}} |
| 253 | Vous pouvez visualiser le réseau Booléen avec la commande: |
| 254 | {{{ |
| 255 | > xv etou.gif |
| 256 | }}} |
| 257 | Vous pouvez visualiser le chronogramme résulat de la simulation avec la commande: |
| 258 | {{{ |
| 259 | > xpat -l etou |
| 260 | }}} |
| 261 | |
| 262 | == C2) Construction réseau Booléen de l'additionneur == |
| 263 | |
| 264 | En vous inspirant du fichier ''etou.c'', écrivez le fichier ''adder.c'' qui construit en mémoire |
| 265 | le réseau Booléen correspondant au circuit additionneur 2 bits décrit ci-dessus. |
| 266 | On utilisera pour cela les fonctions cons_boolnet(), cons_process() et cons_signal(). |
| 267 | On prendra une valeur de 1 ns pour le temps de propagation de la porte NAND2, et de 2 ns pour la porte XOR2. |
| 268 | Pour vérifier la structure du réseau Booléen, on utilisera la fonction ''drive_boolnet()''. |
| 269 | Cette fonction construit une représentation graphique du réseau Booléen, et la sauvegarde dans un fichier |
| 270 | au format .gif ou .ps. |
| 271 | |
| 272 | Modifiez le Makefile permettant de compiler ce programme ''adder.c'', et exécutez-le. |
| 273 | |
| 274 | == C3) Construction et initialisation de l’échéancier == |
| 275 | |
| 276 | Complêter le fichier ''adder.c'' main() pour créer l’échéancier |
| 277 | et initialiser les événements sur les signaux d’ entrée a0, b0, c0, a1 et b1 de façon à respecter le chronogramme ci-dessous. |
| 278 | On utilisera les fonctions cons-scheduler() et add_event(). Attention : le passage de la valeur U (indéfinie) à une valeur 0 ou 1 |
| 279 | constitue un événement : Dans ce chronogramme, il y a donc un événement sur tous les signaux d’entrée au temps T = 0. |
| 280 | |
| 281 | [[Image(chronogramme.png, nolink)]] |
| 282 | |
| 283 | Pour vérifier que l’échéancier est correctement initialisé, on pourra utiliser la fonction ''drive_scheduler ()''. |
| 284 | Cette fonction peut être utilisée avant même l'exécution de la fonction de simulation, pour générer un fichier au format .pat |
| 285 | qui décrit le chronogramme des signaux d'entrée. Vous pouvez visualiser ce chronogramme avec l’outil XPAT. |
| 286 | |
| 287 | == C4) Simulation effective du circuit additionneur == |
| 288 | |
| 289 | Introduisez dans le fichier ''adder.c'' la fonction ''simulate()'' qui effectue la simulation du réseau Booléen, |
| 290 | jusqu'à ce qu'il n'y ait plus aucun événement à traiter dans l'échéancier. Compilez ce programme, |
| 291 | et analysez le chronogramme résultant |
| 292 | |
| 293 | == C5) Ecriture de la boucle de simulation == |
| 294 | |
| 295 | Ecrire en langage C la fonction ''simulate()''. Cette fonction contient une boucle ''while'' qui enchaîne les |
| 296 | deux phases “update” et “exécute” de l'algorithme de simulation ''event-driven'' présenté en cours. |
| 297 | {{{ |
| 298 | void simulate (boolnet_t * bn, scheduler_t * sch) |
| 299 | }}} |
| 300 | On utilisera pour cela les fonctions get_events(), update_tc(), update_signals(), get_process() et eval_process(). |
| 301 | On sauvera sur disque le contenu de l’échéancier en sortie de la boucle de simulation en utilisant la fonction drive_scheduler(), |
| 302 | de façon à visualiser le chronogramme avec l’outil XPAT. |
| 303 | |
| 304 | == C6) Ecriture des fonctions d’accès == |
| 305 | |
| 306 | Bien que le texte de cette question soit très court, cette question est évidemment la plus importante du TME : |
| 307 | Ecrivez vous-même le code des 11 fonctions d’accès aux structures de données du simulateur décrites |
| 308 | dans la section B, et introduire progressivement votre code à la place des fichiers .o qui vous ont été fournis. |
| 309 | |
| 310 | = Compte-Rendu = |
| 311 | |
| 312 | Il ne vous est pas demandé de compte-rendu écrit pour ce TME, mais vous devrez faire une démonstration |
| 313 | de votre code au début du prochain TME. |
| 314 | |