1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
\chapter{Description des solutions technologiques}
\section{Meta-Parseur}
%description du meta langage
%format du fichier instructions.txt
%description du langage asm...
Le meta langage a été développé comme nous l'avons proposé dans notre analyse, et voici sa decription complète\\
Le fichier écrit en méta langage doit être écrit et analysé ligne par ligne, chaque ligne ayant une signification
discincte suivant son premier caractère:
\begin{itemize}
\item F sert à décrire un champ de bits. Le champ appelé 'I' est le champ d'instruction principal et sera utilisé pour
créer les instructions finales. Sa syntaxe est la suivante://
\begin{verbatim}
Fnom:champ1,taille1;champ2;taille2;...
\end{verbatim}
Par exemple, notre FI:c3,6;c2,6;c1,6;e,6;op,8 nous sert à décrire parfaitement le champ d'opcode général tel que décrit
dans notre analyse. c1, c2, et c3 sont les noms des trois champs arguments, e est le nom du champ d'extension, et op est
le nom du champ d'opcode.
\item P sert à décrire une "pattern". Toute instruction va suivre un schéma particulier, et nous pouvons le quantifier grâce
à ce système de partition. La variable générée par l'évaluation d'une pattern sera égale à l'indice de la chaîne trouvée
dans la pattern. Voici la syntaxe d'une telle ligne:
\begin{verbatim}
Pnom:pattern1;pattern2;pattern3;...
\end{verbatim}
Chaque pattern possède elle même une syntaxe propre pouvant se classifier en 5 groupes principaux:
\begin{itemize}
\item Une chaîne de caractères qui sera reconnue telle quelle.
\item Une autre pattern qui sera stoquée dans une variable suivant la syntaxe: nomvar=.Pnom
\item Une déclaration d'adresse stoquée dans une valeur immédiate suivant la syntaxe: .I=.O
\item Une déclaration de décalage (utilisant l'opérateur [) qui peut se faire soit par une pattern immédiate sous la syntaxe [nomvar=.Pnom
soit par le décalage d'une autre adresse sous la syntaxe .I=.O[nomvar=.Pnom
\end{itemize}
Ainsi nous avons une pattern pour les registres (Pr:R0;R1;R2;...) et une pattern pour toutes les adresses (Pm::regop=.Pr;.I=.O;[regop=.Pr;.I=.O[regop=.Pr)
et les numéros d'indices de chaque élément correspond bien aux spécifications données dans notre analyse.
\item I sert à décrire une instruction. Une ligne I est décomposée en deux partie. La ligne ne suit pas de syntaxe particulière dans sa première partie,
car nous devons pouvoir décrire n'importe quelle style d'instruction. Par exemple, nous avons I:ADD c1=.Pr,c2=.Pr,c3=.Pr; qui signifie que nous décrivons
l'instruction ADD suivie de trois registres dont les indices correspondants seront mis dans les variables c1, c2 et c3 respectivement. La deuxième partie
de la ligne (après le ;) sert à donner la listes des variables implicites. Dans l'exemple de notre ADD, nous avons ceci: op=0x0;e=0x0 ce qui signifie que les
variable op et e doivent être mises à zéro. Ainsi, chaque ligne sera évaluée récursivement en fonction des différentes variables et des différents champs spécifiés.
Dans notre cas, comme nous avons FI qui déclare les variables c1, c2, c3, e, et op, il nous faut obligatoirement ces 5 variables pour chaque instruction.
Enfin, chaque élément de l'instruction peut suivre aussi des formes différentes. En voici la liste:
\begin{itemize}
\item Nous pouvons avoir une chaîne toute seule (comme ADD dans notre cas) pour dire qu'il faut que cette chaîne soit présente dans
le code source assembleur.
\item Nous pouvons avoir comme nous l'avons vu la syntaxe variable=.Pnom. Cela signifie qu'à cet endroit de la ligne source assembleur
doit se trouver un mot pouvant évaluer la pattern, et qu'il faut placer l'indice évalué dans la variable spécifiée.
\item Nous pouvois avoir .I=.C ce qui signifie qu`à cet endroit là doit se trouver une constante, qui sera placée immédiatement après
l'opcode de l'instruction.
\item Nous pouvons enfin avoir .I=.O ce qui signifie qu'à cet endroit là doit se trouver une adresse directe.
\end{itemize}
Dans la liste des instructions de la seconde partie, nous pouvons avoir aussi la déclaration variable=.Fnom, ce qui signifie que la variable
indiquée sera évaluée par le champ de bit spécifié. Enfin nous pouvons aussi avoir la déclaration variable1=variable2.
\end{itemize}
Ce meta langage nous a permi de décrire une grande partie des instructions dont nous avons parlé dans l'analyse. Voici ce fichier:
\verbatiminput{../samples/instructions.txt}
\section{Parseur}
Le parseur est un morceau de code relativement simple. Il va lire une ligne en entrée et va la décomposer en deux catégorie d'éléments:
\begin{itemize}
\item Des symboles
\item Des opérateurs
\end{itemize}
Tout est traité suivant une pile polonaise inversée. Par exemple, l'expression 1 + 2 * 3 + 4 sera traitée comme suit:
\begin{verbatim}
PushSymbol(1)
PushSymbol(2)
PushSymbol(3)
ActPile(*)
ActPile(+)
PushSymbol(4)
ActPile(+)
\end{verbatim}
Les fonctions PushSymbol et ActPile sont des symboles globaux et sont définis dans l'assembleur.
\section{Assembleur}
L'assembleur, s'il a l'air complexe par la taille de son code source, est en fait relativement simple aussi. Il est articulé sur un mécanisme
très simple: tout symbole provenant du parseur sera encapsulé dans des bulles qui seront évaluées au fur et à mesure de la progression de
l'assemblage du code source. Au pire, au stade final de l'assemblage, lors de la génération du fichier objet, nous aurons des symboles
que l'éditeur de liens se fera une joie de résoudre et de compléter. Dans tous les cas, l'analyse du fichier se fait sur quatre niveaux:
\begin{itemize}
\item Lors de l'ajout d'un symbole par le parseur.
\item Lors de l'ajout d'un opérateur par le parseur.
\item Lors d'une fin de ligne.
\item Lors de la fin du fichier source.
\end{itemize}
A chacun de ces quatre niveaux se trouve des paliers d'évaluations. Si un symbole, une expression ou une ligne ne peut pas
passer ce palier, une erreur sera générée. Ces paliers deviennent de plus en plus stricts suivant les niveaux passés. Ainsi, comme décrit
plus haut, le dernier palier ne peut que laisser passer des valeurs immédiates ou bien des symboles à reloger par l'éditeur de liens.\\
L'assembleur possède aussi quelques symboles prédéfinis:
\begin{itemize}
\item .text pour déclarer l'écriture dans le segment de text
\item .data pour déclarer l'écriture dans le segment de data
\item .bss pour déclarer l'écriture dans le segment bss
\item .start pour déclarer le point d'entrée
\item DB, DW et DD pour enregistrer un ou plusieurs mots mémoires
\item DS pour enregistrer une chaîne de caractères
\item DR pour enregistrer un tableau de mots non initialisés.
\end{itemize}
\section{Editeur de liens}
Les fichiers objets générés par l'assembleur ont une struture suffisamment simple pour que l'éditeur de liens ait un travail très simplifié:
il charge d'affilé tous les fichiers objets à lier, et va créer une table de tous les symboles internes. Puis il va concaténer tous les
segments de text ensemble, tous les segments de data ensemble, et va reloger les références croisées suivant la table des symboles internes.
Le fichier de sortie est un binaire qui suit les spécification de notre analyse. La table de relogement nous sert à indiquer toutes
les références à des symboles absolus, de sorte à pouvoir placer le binaire n'importe où dans la mémoire de notre CPU virtuel.
\section{Operations ALU} %algos ALU lente
L'ALU est divisée en deux parties : La première qui fait les opérations sur les entiers et la deuxième qui se comporte comme un microprocesseur
c'est-à-dire qu'elle fait des calculs en binaire pour effectuer les différentes opérations qui lui sont demandées.
\paragraph{Descriptifs des opérations sur les entiers :}
Ces fonctions ont été écrites pour pouvoir tester l'Unité de contrôle, elles sont implementées en C, ce qui permet d'utiliser les opérations
traditionnelles (comme l'addition, soustraction, multiplication, division, ) de manière simplifiée et relativement rapide.
\paragraph{Descriptifs des opérations en binaire :}
Toutes les fonctions sont écrites sur la meme base, on sait qu'il y a au maximum 32 bits, donc pour faire les opérations, il suffit de parcourir
les 32 bits et pour cela on utilise une simple boucle pour (for en C) qui parcourt les éléments bit après bit.
Pour les fonctions de l'addition et de la soustraction, nous faisons comme dit précédemment les opérations bits après bits en commencant par le
bit de poid faible et terminant sur le bit de poid fort en gérant entre temps une retenue qui si lors de la dernière itération se trouve encore
être utilisée renvoie un flag d'erreur de retenue qui sera géré par le simulateur et qui dans ce cas retourne tout de meme le début du résultat,
et si tous c est bien passe, le résultat sera stocké dans le registre correspondant.
En ce qui concerne la multiplication, nous utilisons l'algorithme que nous avons apris a l'école primaire : c'est-à-dire la multiplication d'un
facteur par un nombre de l'autre, puis on additionne les résultats successifs. Si jamais lors de l'addition il y a dépassement nous utilisons un
deuxième registre qui sera incrémenté de la valeur demandée.
Pour la division, nous utilisons comme pour la multiplication deux registres, un pour stocker le résultat et l'autre pour y mettre le reste de la
division et pour ce faire on utilise l'algorithme de notre jeunesse.
Pour les deux fonctions de décalage, nous décalons les bits du nombre demandé et on remplace les bits manquants par 0.
\section{MiniOS}
Notre MiniOS n'a qu'un role très restreint: celui de charger un binaire et le reloger correctement en mémoire, et d'effectuer
des petits travaux d'interfaces homme/machine. Toutes les fonctions d'interface pensées pendant l'analyse ont été implémentées.
Lors du chargement d'un binaire, il va initialiser correctement toutes les informations nécessaires au bon déroulement du programme.
\section{Contrôleur}
Le contrôleur va prendre la main dès qu'un binaire aura été chargé en mémoire. Il va décoder le binaire instructions par instructions,
et va activer les différents composants de notre CPU: les registres, l'alu et la mémoire. L'accès à la mémoire fait l'objet d'un code
très spécifique, car c'est en écrivant à certains offsets mémoire que nous pouvons récupérer des informations devant aller vers le MiniOS.
|