
De la Compilation à l'Exécution : Conception d'un Système Complet (Compilateur + Processeur)


Dans le cadre d'un projet d'ingénierie, nous avons eu l'opportunité de concevoir un système informatique de bout en bout : depuis la définition d'un langage source haut niveau jusqu'à son exécution simulée sur un processeur pipeliné. Ce projet nous a permis d'explorer toutes les strates de l'informatique système : compilation, assembleur, architecture matérielle, gestion de la mémoire, exécution d'instructions et même la détection d'aléas de données.
Objectif du projet
Notre travail reposait sur deux piliers principaux :
- Créer un compilateur traduisant un langage proche du C en un assembleur orienté mémoire.
- Concevoir un processeur capable d'exécuter un code assembleur orienté registre, via une architecture pipeline.
Entre ces deux mondes, nous avons également développé un cross-compilateur et un interpréteur, ce qui a permis de chaîner toutes les étapes de transformation d'un programme.

Partie I - Conception du compilateur (LEX/YACC)
Le langage source est volontairement simple mais proche du C : on y retrouve les types int
et bool
, des conditions, des boucles (for
, while
, do-while
) et la plupart des opérateurs usuels.
Une grande attention a été portée à l'optimisation du code intermédiaire, notamment dans la gestion des variables temporaires.
Exemple C | Code Non Optimisé | Code Optimisé |
---|---|---|
int a = 2; | MOVi 0 2 MOV 1 0 // @a = 1 | MOVi 0 2 // @a = 0 |
int a = 2 + 2; | MOVi 0 2 MOVi 1 2 ADD 2 0 1 | MOVi 0 2 MOVi 1 2 ADD 0 0 1 |
bool a = (4+5) <= (10-1) | ... | LE 0 0 1 // @a = 0 |
Les blocs if/else
, for
, et while
sont gérés avec des adresses temporaires que le compilateur remplit dynamiquement après le parsing, garantissant une exécution fluide sans sauts erronés.
Partie II - Assembleur orienté mémoire & interpréteur
Le format de sortie du compilateur suit une structure fixe OP @A @B @C
, chaque instruction étant codée sur 32 bits. Voici les instructions les plus utilisées :
Catégorie | Instructions disponibles |
---|---|
Arithmétiques | ADD , SUB , MUL , DIV , MOD |
Logiques | AND , OR , NOT |
Comparateurs | EQ , NEQ , LT , LE , GT , GE |
Contrôle de flux | JMP , JMPZ , JMPNZ , CALL , RET |
Divers | MOV , MOVi , PRI , INCO , DECO |
L'interpréteur exécute ce code assembleur ligne par ligne en simulant une mémoire, un pointeur d'instruction, et les opérations classiques de traitement.
Partie III - Conception du processeur pipeliné
Nous avons développé une architecture à 5 étages : Fetch, Decode, Execute, Memory et Write Back. Chaque étape est implémentée de manière modulaire, avec une gestion fine des conflits de registres.
Parmi les composants clés :
- L'ALU réalise toutes les opérations, en tenant compte des flags
Z
,N
,O
,C
. - La ROM contient les instructions à exécuter.
- La RAM permet le stockage temporaire.
- Un banc de 16 registres stocke les données 8 bits.
- Le
data_hazards_manager
détecte les lectures concurrentes de registres et insère des NOP si nécessaire.

Partie IV - Cross-compilation (mémoire → registre)
Le compilateur initial génère du code orienté mémoire. Pour exécuter ce code sur notre processeur orienté registre, nous avons conçu un cross-compilateur Python.
Le principe : remplacer chaque instruction mémoire par une séquence registre équivalente, en insérant des instructions de chargement et de sauvegarde.
Code mémoire | Code registre équivalent |
---|---|
ADD @A @B @C | LDR 0 @B LDR 1 @C ADD 1 1 0 STR @A 1 |
Ce mécanisme permet de transformer automatiquement n'importe quel code généré par notre compilateur en un programme exécutable sur le processeur.
Partie V - Intégration finale et tests
Après le développement modulaire des composants, nous avons validé l'intégration complète :
- Compilation d'un programme source vers assembleur mémoire
- Transformation par le cross-compilateur vers assembleur registre
- Exécution du code sur le simulateur pipeline
Chaque étape produit un fichier de sortie qui devient l'entrée de l'étape suivante, permettant une chaîné de compilation fiable.
Conclusion
Ce projet fut une expérience intense et formatrice. Il nous a permis de toucher à la fois aux couches logicielles (analyse lexicale, syntaxique, sémantique, génération de code) et matérielles (architecture, exécution, pipeline, registres).
Nous en retenons des compétences clés :
- Conception d'un langage avec LEX/YACC
- Génération de code optimisé et structuré
- Simulation d'un pipeline et gestion des aléas de données
- Transformation d'architectures mémoire vers registre
Ce travail reflète notre capacité à concevoir, coder, tester et intégrer des systèmes complexes.
Remerciements
Nous remercions nos encadrants pour leur accompagnement tout au long du projet. Le travail présenté ici a été réalisé intégralement par notre binôme, du compilateur au processeur, avec rigueur et passion.
Vous pouvez retrouver le code source complet de ce projet sur notre GitHub