| | 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 | |