Ce sujet contient deux parties. Dans une première partie nous introduisons un assembleur qui permet d'écrire des programmes en langage d'assemblage, ce qui est beaucoup plus pratique que de les écrire en langage machine. C'est un premier pas vers les langages de haut niveau qui rendent l'utilisation d'ordinateur plus confortable. Dans une seconde partie, nous nous intéresserons à l'utilisation des interruptions pour exécuter "en parallèle" plusieurs programmes. Nous n'avons ici plus de nouvelles instructions ni de modification du chemin de données, l'architecture que nous avons construite jusque là dispose de tout ce qu'il nous faut :archi_ordonnanceur.circ, csmetz2015.jar, microcode_ordonnanceur.rom
L'architecture est représentée ci-dessous et je vous rappelle que vous disposez de la carte de référence de l'architecture.
La programmation en langage machine est assez fastidieuse pour plusieurs raisons :
LDAi 3 STA 1001 ...Chaque ligne contient au plus une instruction. Les valeurs qui suivent les instructions doivent être hexadécimales. Le langage d'assemblage accepte les commentaires, préfixés de ";" :
LDAi 3 ; ceci est un commentaire : on charge 3 dans le registre AOn peut utiliser des étiquettes pour référencer des lignes du programme :
JMP init JMP bouton init: LDSPi @stack@ STI LDAi 1 LDBi 1 loop: ADDA STA 1001 JMP loopLe mot clef "@stack@" est réservé. Il est remplacé, par le script python, par l'adresse de la pile que l'assembleur a calculée. Si vous souhaitez placer la pile vous-même, rien ne vous empêche de ne pas utiliser le mot-clef @stack@. Si vous avez besoin de stocker des variables globales en mémoire, vous utiliserez l'instruction DSW qui réserve un mot mémoire et lui associe une étiquette :
DSW compteur LDAi 0 STA compteurPar convention, on allouera les variables globales au début du programme, après les vecteurs d'interruptions. Il est interdit d'utiliser des noms de variable ou des étiquettes qui peuvent s'interpréter comme une valeur hexadécimale. Par exemple, vous ne pouvez pas écrire
DSW ffLa disposition en mémoire de la pile et des variables globales est réalisée par notre assembleur assemble.py de la manière suivante :
... 0x0ffb <--- PILE 0x0ffc <--- PILE 0x0ffd <--- PILE 0x0ffe <--- variable globale 2 0x0fff <--- variable globale 1 0x1000 <--- cette adresse référence le premier afficheurDans l'exemple ci-dessus, on a supposé que deux variables globales avaient été définies, le pointeur de pile calculé sera alors @stack@=0x0ffd. En effet, l'assembleur s'arrangera toujours pour placer la pile à l'adresse la plus élevée adressable en RAM, avant les variables globales.
Pour traduire le programme assembleur en code machine, on invoquera le script assemble.py en passant en argument le nom du script assembleur et le nom du fichier mémoire cible:
python assemble.py monscript.asm monscript.memLe fichier monscript.mem est alors généré et peut être directement chargé en RAM sauf si des erreurs ont été produites pendant l'assemblage. Par exemple, le programme un_compteur.asm qui incrémente un compteur et l'affiche se réécrit en un_compteur.mem une fois assemblé.
Pour vous faire la main avec la programmation assembleur, je vous propose d'écrire les programmes ci-dessous en assembleur et les traduire en langage machine avec le script python.
Supposons que vous ayez besoin d'exécuter plusieurs programmes de manière simultanée, par exemple surfer sur internet en même temps que vous imprimez un document. Vous n'avez certainement pas envie d'attendre que l'impression se termine pour pouvoir récupérer la main sur la machine pour continuer de surfer sur Internet. Comme notre machine est mono-coeur, il faut disposer d'un moyen d'allouer alternativement le chemin de données à l'un et l'autre des programmes à exécuter. C'est le rôle de l'ordonnanceur (scheduler). On va ici voir une version assez frustre d'ordonnancement mais qui donnera néanmoins l'illusion que deux programmes tournent en parallèle. Pour cela, il faut allouer alternativement le processeur à l'un ou l'autre des programmes (on pourrait considérer plus que deux programmes).
Ce qui va nous intéresser ici c'est la commutation de contexte. Le contexte est défini par l'état de tous les registres A, B, PC. En fait, toute l'astuce va consister à changer la valeur du pointeur de pile et les mécanismes introduits jusqu'à maintenant de départ et de retour d'interruption vont faire le reste du travail.
On souhaite exécuter deux programmes qu'on appelle programme0 et programme1.Supposons qu'un premier programme soit entrain de tourner (on verra un peu plus loin comment initialiser le système). Ce programme est entrain de tourner avec sa propre pile, appelons là pile0. Lorsque l'interruption est levée, le départ en interruption sauvegarde les registres sur pile1 puis le vecteur d'interruption
Il reste la question de savoir comment initialiser la machine. Pour que l'ordonnanceur puisse faire son travail, il doit disposer de 3 variables :
Ecrivez et testez un programme assembleur qui fait tourner deux programmes :
Si vous avez terminé :