Cartouche

Champ Valeur
Auteur·e Élise
Édition 2024-11-14
Durée 3 semaines
Taille des équipes 2~3 personnes
Rendu via git, dépôt $YEAR_spice, droits en lecture à delivery_collector

Règlement

Prenez connaissance du règlement ici

L’intégralité de la bibliothèque standard est autorisée. Vous êtes encouragés à vous servir des outils qu’elle vous propose s’ils résolvent un de vos problèmes. Votre objectif c’est d’éviter de ré-inventer la roue quand ça n’est pas nécessaire.

Certains crates sont proposés dans le sujet. Vous avez le droit de vous servir de toutes leurs fonctionnalités.

Vous pouvez rechercher des alternatives à ces crates, voir des crates qui vous proposent des fonctionnalités complètement différentes. Si ces crates affectent l’écriture de votre algorithme ou l’architecture de votre programme, vous devez les faire valider en amont.

Barème

Critères Points accessibles
Simulation 3
Shell de commandes (simulation, input=value, display, loop, debug) 3
Composants implémentés 3
Parsing fichier de configuration 3
Qualité du code, facilité de lecture 3
Architecture, découpage 3
Prévention des boucles infinies 2
Malus maximum (non respect du trait, ré-interprétation d’une consigne, etc.) -5
Total 20

Introduction

Jusqu’à maintenant, vous avez programmé dans plusieurs langages compilés comme le C ou le Rust. Ils sont compilés en assembleur qui fini lui-même transformé en langage machine pour être compris par un microprocesseur.

Si on descend encore plus bas que les microprocesseur on trouve les chipset (lit. jeux de puces) et les puces électroniques elles-mêmes (ou circuits intégrés).

<img src=“https://upload.wikimedia.org/wikipedia/commons/8/80/Three_IC_circuit_chips.JPG” width=“300px” >

Les puces électroniques reproduisent elles-mêmes le comportement de circuits formés à partir de composants électroniques de base comme des diodes et des transistors.

En assemblant ces composants on est capable de fabriquer des portes logiques. Vous devez déjà avoir entendu parler de la porte ET ou la porte OU. Elles prennent toutes les deux deux entrées et ont chacune une sortie. Ces entrées et sorties sont des broches physiques de la puce (qu’on appelle pin en anglais).

Les états vrai et faux se distinguent par un potentiel électrique (un voltage) différent. En fonction du circuit sur lequel on travaille, vrai peut correspondre à 3 V, 5 V ou 12 V (en fonction de VCC). Faux est en général défini par VSS qui est en général mis à la masse, à 0 volts. VCC et VSS sont nos tensions vraie et fausses de référence.

Lorsqu’on étudie et que l’on travaille avec des portes logiques et des circuits électroniques, on utilise des tables de vérité pour déterminer la sortie que l’on attend pour chaque combinaison d’entrées.

Voici par exemple la table de vérité d’une porte ET :

Entrée A Entrée B Sortie S
0 0 0
1 0 0
0 1 0
1 1 1

On voit que la porte ET a une sortie de 1 seulement lorsque ses deux entrées sont a 1. Exactement comme lorsqu’on écrit une condition avec un && dans les langages qui héritent leur syntaxe du C.

La table de vérité d’une diode toute seule, ou d’un fil, ressemble à ça :

E (entrée) S (sortie)
0 0
1 1

Exercice : porte OU

Écrivez la table de vérité d’une porte OU.

Pensez à un if avec un opérateur || :

// when does this return true? or false?
if input_a || input_b {
	return true;
} else {
	return faise;
}

Exemple de puce

La puce 4081, dont vous avez la documentation, comprend 4 portes ET.

Pasted image 20241114152552.png

La première porte ET a ses entrées A et B sur les broches 1 et 2. Sa sortie est sur la broche 3.

Indice de la porte Borche A Broche B Broche en sortie
1 1 2 3
2 6 5 4
3 8 9 10
4 13 12 11
Sur la broche 7, on trouve le VSS qui se relie à la borne négative de l’alimentation. Sur la broche 14, le VDD qui se relie à la borne positive de l’alimentation. Mais nous n’en aurons pas besoin car nous n’allons pas simuler la physique, ou l’électronique, seulement la logique booléenne.

Fichier de configuration

.chipsets:
input i0
input i1
4081 and_chip
output out

.links:
i0/1 and_chip/1
i1/1 and_chip/2
and_chip/3 out/1

Dans .chipsets on déclare les chipsets sur lesquels la situation doit travailler et on leur donne un nom.

Dans .links on indique quelles broches de quel composants doivent être reliées par fils. On fait références au composants par le nom qu’on leur a donné dans .chipsets

Trait des composants

https://git.ecole-89.com/eriizu/red_spice_traits

https://crates.io/crates/red_spice_traits

Vos composants doivent tous êtres des structures (avec implémentation) différentes. Ils doivent tous implémenter un même trait, le trait Component.

Le trait Component doit prévoir les méthodes suivantes :

  • model renvoie le nom du composant ;
  • simulate résout l’état des sorties en fonction des entrées et des composants qui y sont reliés en appelant leur méthode simulate ;
  • get_pin_state renvoie l’état d’une broche ;
  • set_link lie une broche (en entrée) de ce composant à la broche (en sortie) d’un autre ;

Pour le stockage et les liens entre les composants, vous aurez besoin d’utiliser :

  • Rc reference counter,
  • RefCell, et
  • Box pour stocker des éléments de taille différentes qui implémentent un même trait.

Chaque composant doit implémenter std::fmt::Debug en affichant son état interne (chaque entrée ou sortie telle qu’initialisée ou calculée précédemment) et ses liens.

Lorsque vous appelez simulate sur un composant, il doit résoudre l’état de ses sorties en fonction de ses entrées. Si l’entrée d’une porte n’est reliée à rien, sa sortie est non-définie. Si l’entrée d’une porte est reliée à un autre composant, sa méthode simulate doit être appelée.

Sur les composants spéciaux, la méthode simulate n’a aucun effet, sauf pour output qui va se contenter d’appeler le simulate du composant auquel il est relié puis stocker la valeur de la broche à laquelle il est relié.

Composants à implémenter

Pour ce projet vous devez simuler les composants suivants :

  • 4001 NOR
  • 4011 NAND
  • 4013 Bascules
  • 4030 XOR
  • 4069 NOT
  • 4071 OR
  • 4081 AND

Leur documentation se trouve ici, et est accessible depuis le réseau de l’école

Vous devez aussi implémenter des composants spéciaux :

  • input : une unique broche dont l’état peut être défini avant la simulation ;
  • output : une unique broche dont l’état peut être consulté après la simulation ;
  • clock : une unique broche dont l’état change d’une simulation à l’autre (0 puis 1 puis 0 puis 1, etc.) ;
  • true : une unique broche dont l’état est toujours vrai ;
  • false : une seule broche dont l’état est toujours faux.

Shell de commandes (runtime)

Votre simulateur doit pouvoir être démarré avec un fichier de configuration en argument. Il doit prendre en charge les commandes suivantes :

  • exit : quitte le programme ;
  • display : affiche le numéro d’itération de la simulation et l’état des entrées et sorties du circuit ;
  • input=valeur : affecte à une entrée la valeur donnée (soit 0, 1 ou U) ;
  • simulate : simule une itération du circuit et affecte à la clock son complément ;
  • loop : simule en boucle le circuit jusqu’à ce qu’un signal d’interruption doit reçu ;
  • debug : appelle le dump de tous les composants.

Affichez le prompt > lorsque vous êtes prêts à recevoir une commande.

Une entrée ou sortie non définie s’affiche U.

Une simulation consiste à essayer de définir l’était de toutes les sorties en remontant le graphe.

États logiques

Dans un circuit logique, il est possible que des valeurs indéfinies se glissent. Que ce soit parce qu’un composant à état n’ait pas encore été initialisé lors d’une itération, ou parce qu’une entrée n’a pas été définie par l’utilisateur.

Par exemple : une bascule RS (une mémoire vive d’un seul bit) peut très bien avoir un état vrai ou un état faux au démarrage d’un circuit. On dit que sont état est indéfini tant qu’il n’a pas été initialisé explicitement.

Vous devez représenter les états indéfinis ce qui signifie que vous ne pouvez pas simplement utiliser des booléens.

Servez-vous de l’énumération suivante pour vous représenter l’état des broches des composants.

enum LogicState {
	Undefined,
	False,
	True
}

Si vous essayez de déterminer la sortie d’une porte dont lune des entrées est non-définie, alors sa sortie doit aussi être non-définie.

Explications supplémentaires 2024-12-02

.chipsets:
input i0
input i1
4081 and_chip
output out

.links:
i0/1 and_chip/1
i1/1 and_chip/2
and_chip/3 out/1

Dans ce fichier de configuration on déclare deux input : i0 et i1 dont on doit spécifier les valeurs à l’exécution du simulateur.

On déclare aussi l’existence d’une puce 4081 qu’on appelle and_chip à laquelle on va relier des input et output.

Enfin on déclare un output qui sert au simulateur pour savoir quelle information afficher en sortie mais aussi où commencer la simulation.

Mettons, la broche de i0 est mise à False et la broche de i1 est mise à True.

La simulation se passerai ainsi :

  1. Le simulateur appelle simulate sur notre premier et seul output pour connaitre son état.
  2. Pour connaitre l’état de sa broche, l’output doit connaitre l’état de la broche 3 du composant auquel il est relié (il s’agit de son seul link), il appelle simulate sur se composant le 4081.
    1. Le 4081 pour calculer son état doit connaitre celui des composants auxquels il est relié, il va donc appeler simulate sur chacun d’entre eux (en l’occurrence les input)
      1. Les inputs n’ont rien à faire lors du simulate leur état étant déjà connu.
    2. Le 4081 get_pin_state sur les deux composants liés (les input) et sauvegarde l’état de i0 et i1 comme étant celui de ses broches 1 et 2 respectivement.
    3. Le 4081 calcule l’état de toutes ces sorties en en utilisant celui des broches en entrées. Pour calculer la broche 3 il va faire un ET logique entre les broches 1 et 2. Si l’une des broches est à Undefined alors la sortie l’est aussi. En l’occurrence les broches sont à False et True, donc l’état de la broche 3 prend la valeur False.
  3. L’output get_pin_state la broche 3 du composant auquel il est lié (le 4081) et trouve la valeur False.
  4. La simulation est terminée. La valeur de false pour l’output est affichée.

Qu’est-ce que l’on apprend de cet exemple ?

  • Les liens n’ont besoin de se faire que du côté du composant qui “consomme” l’état. Celui qui est intéressé par l’état. Ainsi sur les output on aura nécessairement un lien vers un autre composant mais AUCUN composant n’aura de lien sur un output.
  • Chaque composant doit avoir un tableau qui représente l’état de ses broches sans nécessairement différencier les entrées des sorties.
  • Chaque composant doit pouvoir générer un état pour ses sorties en fonction de ses entrées.
  • Chaque composant doit, avant de calculer l’état de ses sorties, demander aux composants auxquels il est lié de calculer les siennes. (simulate puis on calcule, se qui provoque une cascade d’appel à simulate avant de calculer quoi que ce soit.)