SOMMAIRE

 

1) GENERALITES 
1.1) Principe du projet
1.2) Caractéristiques
1.3) Ma vision du projet

2) HARDWARE
2.1) Carte de développement
2.2) Verilog, schéma global
2.3) Verilog, coeur CPU
2.4) Verilog, Vidéo VGA
2.5) Verilog, périphériques

3) OUTILS DEVELOPPEMENT SOFTWARE
3.1) Généralités
3.2) Langage A2Z Basic
3.3) Compilateur
3.4) Assembleur
3.5) Autres outils
3.6) Emulateur sur PC

4) LES LOGICIELS A2Z
4.1) Le Boot
4.2) Système fichier & OS
4.3) Editeur texte
4.4) Image viewer & map viewer
4.5) Le jeu : Micromachines

Blog (hackaday.io)

3.3) Compilateur A2Z Basic

C’est un compilateur croisé, tournant sur PC, qui digère du code A2Z_Basic et produit du code Assembleur. Il était hors de question de faire un « compilateur natif » (= qui puisse tourner sur A2Z lui-même), ça aurait été beaucoup trop complexe.

 

Le compilateur, c’est la partie la plus complexe du projet, surtout pour moi qui ne suis pas habitué à coder des applications « lourdes » sur PC. Coder un compilateur, ça nécessite de bien réfléchir, d’organiser son code.

J’ai vraiment adoré coder cette partie, et me creuser la cervelle pour arriver au bout.


Le cheminement séquentiel du compilateur :
1) Stockage des lignes :
On stocke le fichier source (A2Z basic) sous forme d’un tableau de lignes de code source (chaque ligne est une instruction ou déclaration séparée)
2) Configuration :
On cherche la « directive » permettant de configurer l’exécutable : taille mémoire allouée au code et aux variables, et adresse mémoire de départ (l’exécution commence à une adresse fixe).
3) Fonctions, première passe :
On balaye une première fois le code source, à la recherche des fonctions (subfonctions, fonctions ASM, et macros). Dans la pratique, on cherche la première (celle qui déclare la fonction) et la dernière ligne (endfunct) de chaque fonction. Pour chaque ligne de code, on identifie à quelle fonction elle appartient. La première fonction déclarée doit être le « main », et c’est la fonction qui sera exécutée en premier.
4) Gestion des #define :
On remplace les définitions (#define ) par leur valeur réelle. C’est de la manipulation de chaine de caractères, rien de plus.
5) Déclaration des variables :
On balaye une nouvelle fois le code source à la recherche des déclarations de variables. Pour chaque variable, on stocke ses caractéristiques (type, taille 1, taille 2), et la fonction à qui elle appartient. Une variable qui est déclarée en dehors d’une fonction est automatiquement considérée comme « globale » (fonction zéro).  On vérifie en même temps qu’il n’y a pas de doublon de déclaration (au sein d’une même fonction), et que la taille totale de RAM allouable n’est pas dépassée.
6) Allocation des variables :
On alloue ces variables à l’espace RAM dédié (64ko maxi), de manière fixe.  Dans le même temps, les valeurs des constantes sont traitées.
7) Fonctions, 2ieme passe :
Pour chaque déclaration de fonction (donc première ligne de la fonction), on recherche et on mémorise les entrées et sorties de cette fonction. Une fonction peut avoir plusieurs variables d’entrée et plusieurs variables de sortie. Ces variables doivent impérativement être déclarées à l’intérieur la fonction, donc elles doivent appartenir à la fonction.
8) Décodage des instructions :
C’est la partie la plus lourde et la plus complexe. C’est la seule partie qui génère réellement du code exécutable.
Je manipule ici le concept d’expression informatique : une valeur qui peut être élaborée à partir d’une variable, d’une constante, d’un registre (ou cache), ou d’une opération arithmétique ou logique elle-même calculée à partir d’une combinaison de tout ça. L’analyse des expressions est récursive, ce qui permet de traiter des formules et conditions complexes.
Le résultat de cette expression pourra servir à alimenter une variable, un registre (ou cache), un passage de paramètre à une fonction, ou une condition (if / while / for).
Pour toutes les lignes d’instruction, on balaye le code source à la recherche des instructions, et on génère le code exécutable à la volée. On compte également à la volée la taille/position du code exécutable généré, ce qui permettra par la suite de savoir où faire les branchements = renvois d’exécution (renvoi conditionnel et inconditionnel).
Les instructions peuvent être de 4 types :
8.1 : code assembleur
8.2 : assign : c’est l’instruction la plus simple, qui assigne une variable, ou un registre, ou un emplacement CACHE, à partir d’une expression
8.3 : branchement conditionnel ou inconditionnel (if, else, for, while, goto, etc…)
8.4 : appel d’une fonction. Ca se déroule en 3 temps :
8.4.1 : copie des valeurs des entrées de la fonction, depuis la fonction appelante, vers la fonction appelée.
8.4.2 : appel de la fonction à proprement parler. C’est là qu’on gère la pile d’exécution ; pile réalisée en soft, pas de pile hardware.
8.4.3 : copie des valeurs des sorties de la fonction, depuis la fonction appelée vers la fonction appelante.
9) Gestion des branchements = renvois :
Pour tous les renvois (goto, for, next, if while) et appels de fonctions, on donne l’adresse RAM réelle (dans le code exécutable) vers laquelle renvoyer l’exécution. Ce n’est que maintenant qu’on peut le faire, car il faut auparavant avoir compté la position (dans le code exécutable) de chaque instruction.
10) Génération du fichier ASM :
Le fichier ASM généré est une concaténation de 2 choses : le code exécutable, et les constantes.

Remarque : Il existe 3 types de fonctions différentes :

  • Les « subf », des sous fonctions classiques, dont les appels et retours sont gérés dans la pile d’exécution.
  • Les « ASMf », ou « fonctions ASM », même si elles peuvent contenir autre chose que du code ASM. C’est une fonction de dernier niveau, de bas niveau ; elle ne peut pas en appeler une autre. L’adresse de retour du programme n’est pas gérée dans la pile d’exécution, mais dans un emplacement fixe en Cache. Les appels et retours sont donc beaucoup plus rapides à exécuter. C’est très utile pour les fonctions appelées très fréquemment.
  • Les Macros : code répétitif qui est remplacé tel quel dans le code exécutable. Je l’utilise très peu au final.

F4HDK| Janvier 2017
f4hdk_arob_free.fr