Vocabulaire et mémoire
Mémoire programme
Processus de démarrage
Description des instructions
Vocabulaire et listes chaînées
Recherche des instructions
Introduction
L'objet de cette page est de décrire les principes
propres à la gestion de l'ensemble des instructions FORTH
(vocabulaire) dans mon système en les justifiant. Cette
description est découpée en plusieurs paragraphes:
Attention, bien que mon système soit complètemen
basé sur le FORTH, il est indispensable de bien étudier
le contenu de cette page pour pouvoir bénéficier
des performances optimales du processeur.
Mémoire programme
Si l'on fait abstraction des différentes mémoires
de masse (disque dur, ...) on peut considérer que la carte
d'un ordinateur possède 3 types de mémoires pouvan
contenir à la fois programmes et données:
- la mémoire morte ou permanente très lente
permettant le démarrage du système à la
mise sous tension,
- la mémoire vive statique très rapide mais
de faible capacité,
- la mémoire vive dynamique de forte capacité
mais relativement lente.
Toute l'astuce consiste à optimiser la localisation
des programmes et données dans ces différentes mémoires
en fonctions des performances désirées.
Processus de démarrage
La mémoire morte contient le minimum de programme
permettant de démarrer le système:
- les différents codes logiques nécessaires
à l'initialisation du processeur (vecteur de "RESET",
adresses des piles...),
- le programme et le code pour le téléchargemen
des FPGAs indispensables pour l'accès à la mémoire
vive et aux systèmes de fichiers contenant l'ensemble
des autres programmes.
Le processus de démarrage est donc contenu dans la
mémoire morte qui a pour inconvénient d'être
très lente ce qui implique le chargement du noyau FORTH
dans la mémoire vive. Dès que ce chargement es
réalisé, le processeur n'utilise plus la mémoire
morte afin de bénéficier du maximum de vitesse d'exécution.
Par définition, le noyau FORTH contient l'ensemble
des instructions les plus utilisées dans n'importe quel
programme. C'est pour cela qu'il est chargé dans la mémoire
vive statique qui contient aussi le noyau temps réel, les
variables système ainsi que les piles de données
et de retour.
Etant donné que la mémoire vive statique es
de faible capacité (il y en a quand même 512 KiloOctets
!), toute nouvelle instruction est compilée par défau
dans la mémoire vive dynamique. Une directive "ZONE_RAPIDE"
permet au programmeur d'ajouter des instructions dans la mémoire
vive statique pour optimiser son code (primitives graphiques ou
arithmétiques par exemple). Cette primitive doit être
située juste avant la définition de l'instruction
devant être optimisée en vitesse. Dès la fin
de la définition de l'instruction, le compilateur repasse
en mémoire vive dynamique.
Description des instructions
Chacune des instructions compilées dans la mémoire
est composée selon le tableau suivant:
Nb. d'octets
|
Signification
|
4
|
Ces 4 octets contiennen
le décalage en nombre d'octets entre l'adresse de l'entête
qui suit et celle de l'entête de l'instruction précédente.
Ce nombre est toujours pair. Il est à 0 lorsqu'il s'agi
de la première instruction. |
1
|
Cet octet renseigne le compilateur sur les caractéristiques
de l'instruction:
- bit 7 : mis à 1 si l'instruction est immédiate
(c'est à dire exécutée en mode compilation),
- bit 6 : mis à 1 si l'instruction est directe (c'es
à dire que son contenu est directement compilé
dans l'instruction qui l'utilise en mode compilation),
- bits 5 à 0 : ils contiennent le nombre de caractères
du nom de l'instruction (1 à 63).
On peut utiliser cette adresse pour afficher la chaîne
de caractère du nom de l'instruction en utilisant la séquence
"COUNT 63 AND TYPE".
|
1 à 63
|
C'est la chaîne de
caractère donnant le nom en code ASCII de l'instruction. |
0 ou 1
|
Cet octet est ajouté avec
la valeur 0 si son adresse est impaire. Le processeur ne peu
en effet exécuter du code que si celui-ci est stocké
dans des adresses paires. |
2*n
|
C'est le code de l'instruction
écrit dans le langage du processeur. Il est toujours terminé
par le code de retour de sous-programme. |
Les 4 premiers octets correspondent à la valeur "HERE"
plus 4 moins celle de la variable "START" au momen
de la compilation de l'instruction précédente. Lorsqu'une
nouvelle instruction est crée par le mot ":",
le contenu de la variable "START" est soustrait à
la valeur "HERE" puis incrémenté de 4
et compilé ("HERE" est alors incrémenté
de 4 à son tour). Le contenu de la variable "START"
est alors remplacé par la valeur "HERE". La chaîne
de caractère suivi du code sont ensuite compilés.
L'instruction "IMMEDIAT" positionne le bit 7 du
premier octet à "1" si l'instruction doit être
exécutée en mode compilation.
L'instruction "DIRECT" positionne le bit 6 du
premier octet à "1" si l'instruction doit être
compilée directement et non pas appelée par une
instruction de saut à sous-programme. Ceci est indispensable
pour bénéficier pleinement des performances du processeur
pour les instructions de base ("DUP", "DROP",
"+", ...). Dans le cas d'un processeur non FORTH comme
le MC68030, l'utilisation d'une de ces instructions équivau
à écrire un programme en assembleur.
Si les bits 7 et 6 de cet octet sont tous les 2 à
"1", il s'agit alors d'une instruction qui sera exécutée
en mode interprétation ET en mode compilation.
La chaîne de caractère comportant le nom de
l'instruction est du type de celle manipulable directement par
n'importe quel noyau FORTH. Sa longueur minimale est de 1 (instructions
"+", "-", "/", "*", ...)
et est plafonnée à 63 (longueur qui est relativemen
peu pratique à utiliser mais qui sait...). Il est éviden
qu'il ne peut y avoir de caractère "espace".
Un octet est ajouté si l'adresse suivant la chaîne
de caractère est impaire pour permettre de démarrer
le code à une adresse paire comme l'exige le processeur
(octet de "padding" pour les anglophobes).
Suit enfin le code propre à l'instruction qui es
directement interprétable par le processeur.
Vocabulaire et listes chaînées
Pour comprendre la disposition des différentes instructions
dans la mémoire, voici un tableau présentant un
FORTH vraiment très pauvre car il ne possède que
5 instructions mais, je l'espère, très clair pour
visualiser l'organisation du vocabulaire:
Mémoire vive statique
(exécution rapide)
|
|
0
|
Instruction 1
|
Octet contenant les caractéristiques
1
|
Chaîne de caractère contenan
le nom 1
|
Code 1
|
|
Pointeur sur Instruction
2 - Pointeur sur Instruction 1
|
Instruction 2
|
Octet contenant les caractéristiques
2
|
Chaîne de caractère contenan
le nom 2
|
Code 2
|
|
Pointeur sur Instruction
4 - Pointeur sur Instruction 3
|
Instruction 4
|
Octet contenant les caractéristiques
4
|
Chaîne de caractère contenan
le nom 4
|
Code 4
|
|
...
|
Mémoire vive dynamique
(exécution relativement lente)
|
|
Pointeur sur Instruction
3 - Pointeur sur Instruction 2
|
Instruction 3
|
Octet contenant les caractéristiques
3
|
Chaîne de caractère contenan
le nom 3
|
Code 3
|
|
Pointeur sur Instruction
5 - Pointeur sur Instruction 4
|
Instruction 5
|
Octet contenant les caractéristiques
5
|
Chaîne de caractère contenan
le nom 5
|
Code 5
|
|
...
|
On retrouve bien une liste chaînée d'instructions
constituant le vocabulaire du langage FORTH. Pour retrouver une
instruction, la manière la plus simple est de partir de
la dernière instruction créée (la variable
"START" pointant son adresse) et de remonter jusqu'à
ce que son nom corresponde ou alors jusqu'au début de la
mémoire si elle n'existe pas (un décalage égal
à 0 indique que l'on est arrivé au débu
du vocabulaire).
Cette méthode, très simple à implémenter,
possède l'inconvénient de devenir de plus en plus
lente au fur et à mesure de l'augmentation du nombre des
instructions. C'est ainsi que le FORTH original a été
introduit avec la notion de vocabulaires multiples. Ceci perme
d'optimiser le temps de recherche avec l'inconvénient de
ne pas disposer de l'ensemble des instructions à tout moment.
Mais j'ai abandonné cette idée au profit d'une autre
méthode expliquée dans le paragraphe suivant.
Recherche des instructions
Pour optimiser les temps de compilation, j'ai choisi la
méthode de recherche dichotomique des instructions qui
s'apparente à la méthode utilisée pour chercher
un mot dans un dictionnaire ou une encyclopédie.
Le gros avantage d'un dictionnaire est que tous les mots
sont classés par ordre alphabétique ce qui perme
à quiconque connaissant l'alphabet de trouver un mot en
éliminant la moitié des pages à chaque comparaison
par rapport aux mots examinés. Pour simplifier, si l'on
possède un dictionnaire de 1000 pages, sachez que vous
n'aurez à examiner qu'un nombre maximum de 10 pages pour
trouver le mot qui vous intéresse (logarithme en base 2
de 1000 pour les matheux).
Afin d'appliquer cette méthode très efficace
au vocabulaire FORTH, il suffit d'avoir une représentation
des instructions par ordre alphabétique ce qui se fai
très simplement avec une table contenant la liste de tous
les pointeurs des instructions, pointeurs classés dans
l'ordre alphabétique du nom des instructions.
Pour éclaircir ce discours, rien ne vaut une petite
démonstration:
Supposons un vocabulaire FORTH constitué de seulemen
10 instructions nommée chacune avec les 10 premières
lettres de l'alphabet et dans l'ordre (plutôt désordre)
suivant : "H", "B", "I", "A",
"D", "F", "C", "E", "G"
et "J" (la première instruction définie
est "H" et la dernière est "J"). Si
je veux exécuter l'instruction "A", l'interpréteur
doit partir de "J" et faire 7 comparaisons avant de
trouver l'instruction voulue.
En faisant un calcul simple, on peut estimer le nombre de
comparaisons moyen à: 10(recherche de "H")+9(recherche
de "B")+...+1(recherche de "J") le tout divisé
par 10(nombre d'instructions) ce qui correspond à 5,5.
Maintenant, réalisons une table contenant les adresses
de chacune de ces instructions classées dans l'ordre alphabétique:
Nombre d'instruction
: 10
|
Adresse de l'instruction "A"
|
Adresse de l'instruction "B"
|
Adresse de l'instruction "C"
|
Adresse de l'instruction "D"
|
Adresse de l'instruction "E"
|
Adresse de l'instruction "F"
|
Adresse de l'instruction "G"
|
Adresse de l'instruction "H"
|
Adresse de l'instruction "I"
|
Adresse de l'instruction "J"
|
Pour trouver l'instruction "A", mon algorithme
de recherche va commencer par saisir le nombre d'instructions
soit 10. Il va alors pointer sur la moitié de la table
et se trouver ainsi sur l'adresse de l'instruction "F".
La comparaison des 2 chaînes de caractère va donner
un résultat inférieur à "F". Il
va alors pointer sur la moitié inférieure de la
table soit sur l'adresse de l'instruction "C". Le résulta
de la comparaison étant encore inférieur, l'algorithme
pointe alors sur "B" puis enfin sur "A". Ceci
donne un nombre total de 4 comparaisons à comparer à
7 pour l'algorithme précédent.
Faisons maintenant l'estimation du nombre moyen de comparaisons:
4(recherche de "A")+3(recherche de "B")+2(recherche
de "C")+4(recherche de "D")+3(recherche de
"E")+1(recherche de "F")+4(recherche de "G")+3(recherche
de "H")+2(recherche de "I")+3(recherche de
"J") le tout divisé par 10 ce qui correspond
à 2,9 (à comparer à
5,5 de l'algorithme précédent).
La notion de vocabulaires multiples devient ainsi obsolète.
Le doublement du nombre d'instruction n'introduit qu'une comparaison
supplémentaire !