# Very High Speed Integrated Circuit Hardware Description Language/Multitâche et Système temps réel sur architecture 8 bits

Dans ce chapitre, nous avons l'intention d'examiner un certain nombre de possibilités sur les systèmes multitâches pour des petites architectures 8 bits. Sur les architectures 8 bits il est possible de faire tourner des systèmes importants mais cela nécessite de la mémoire vive. C'est certainement ce qui nous manquera le plus dans nos FPGA même s'il y en a en petite quantité. La majorité des cartes FPGA proposent des RAM externes mais nous ne voulons pas entrer dans le détail de leur gestion.

## Système multitâche non préemptif porté pour ATMega16

Nous allons commencer par essayer un système non préemptif et l'adapter à l'ATMega16 (processeur décrit dans un autre chapitre de ce livre).
Notre point de départ pour cette section est un article sur un système multitâche non préemptif écrit par Ron Kreymborg (en 1999 dans Dr.Dobbs). Ce système a été porté pour les AVR mais le code source original de la page Internet précédente est suffisant.

### Préliminaires

La lecture du code source du système non préemptif nous montre que la gestion du temps se fait à l'aide d'une interruption timer. Cela tombe bien, nous en avons implanté une dans un autre chapitre qui fonctionne à environ 50 Hz. La partie matérielle consiste donc à faire un projet avec l'ATMega16 en utilisant "io_timer.vhd" en lieu et place de "io.vhd". Celui-ci contient un pseudo-timer permettant de réaliser une interruption à une fréquence de 50 Hz pour une horloge à 50 MHz. Il est téléchargeable ICI
Non préemptif ne veut donc pas forcément dire sans interruption timer. Un système préemptif utilise l'interruption timer pour changer de contexte et exécuter ainsi une autre tâche que celle qui était exécutée au moment de l'interruption. Un système non préemptif va tout simplement utiliser l'interruption timer pour gérer le temps (et c’est tout). Le changement de contexte est dans ce cas réalisé par la tâche en cours d'exécution qui le demande explicitement.
Voici le code source C et son commentaire qui montre qu'une interruption timer est nécessaire :
Nous sommes donc prêt à réaliser quelques exemples.

### Quelques exemples

La ressource utile pour commencer est donnée maintenant
Prenez ce code et mettez votre interruption et votre "main" entre les prototypes et déclarations et l'implantation des fonctions. Nous utilisons la version de 1999 du code mais une version plus récente est disponible et est décomposée avec un fichier ".h" pour les prototypes.

#### Faire clignoter une LED à 0,5 Hz environ

Puisque le timer fait un dépassement de capacité (overflow) au rythme de 50 Hz, un appel avec un temps de 25 fera environ 0,5 s. Voici donc le code :
L'ajout de tâches supplémentaires se ferait avant le for(;;) et nous avons omis l’ensemble des implémentations des fonctions du noyau que nous avons donné comme ressource un peu plus haut.
Ce code vous montre la présence de deux queues : une pour les tâches prêtes utilisée avec QueTask et une avec les tâches retardées utilisée avec QueDelay. C'est l'interruption qui réalise la gestion des tâches retardées avec DecrementDelay.

#### Faire clignoter deux LEDs

Nous allons faire maintenant clignoter deux LEDs, chacune avec leur tâche associée. Ce problème peu sembler pas très différent du précédent mais il va nous montrer un point qu’il n'était pas nécessaire d'évoquer dans l'exemple précédent.
L'écriture ou l'effacement d'un bit particulier peut se faire avec une instruction du genre "PORTC = PORTC | 0x01;". Mais si vous utilisez cette technique avec notre cœur ATMega16, cela ne fonctionnera pas. Nous avons déjà évoqué ce problème plusieurs fois. Il est lié au fait qu'écrire PORTC à droite d'une affectation veut dire qu'on lit le PORTC en question. Cela fonctionne sur une architecture du commerce mais en ce qui nous concerne nous n'avons pas implanté les PORTs comme cela. Pour passer outre ce problème il suffit d’utiliser une variable interne sur laquelle on travaille systématiquement avant de l'affecter au PORT. Regardez dans le code ci-dessous, vous voyez la déclaration de la variable vportc... qui ne sert qu’à cela.
Il est possible de séparer la mise à jour du PORT (ici PORTC) du calcul de la valeur des deux bits comme ceci :
Vous voyez apparaître une tâche supplémentaire "updatePORTC" qui s'exécute pratiquement sans arrêt (Ne pas mettre "ReRunMe(0)" dans cette tâche de fond, cela bloque les autres tâches).
Il est grand temps de passer maintenant aux systèmes multitâche préemptif.

## Système multitâches préemptif

Les systèmes Multitâches préemptifs ont leur article dans Wikipédia : Multitâche préemptif. La différence essentielle avec la section précédente est que maintenant ce n'est plus la tâche en cours d'exécution qui décide de donner la main à l'ordonnanceur mais une interruption matérielle.
Il est assez facile de trouver un petit système multitâche pour AVR. AtomThreads est un exemple et FemtoOs en est un autre. L'adapter à un cœur VHDL nécessite cependant une bonne connaissance du matériel et du logiciel. La première chose qu’il faut aller voir dans les sources est le timer utilisé.

### FemtoOs

Dans le cas de FemtoOs, c’est le timer 0 qui est utilisé. Comme nous avons déjà utilisé un pseudo timer0 dans ce chapitre, on peut avoir l'impression qu’il suffit simplement de l’utiliser. En fait ce sera plus compliqué que cela.
* D'abord notre timer 0 ne déclenche que l'interruption de débordement... et il faut donc vérifier que ce n’est pas l'interruption de comparaison qui est utilisée (avec un mode CTC du timer0). Si c’est le cas, il faut changer le matériel... ou changer le code
* Notre matériel possède une différence assez subtile avec un AVR du commerce... et comme d'habitude pour changer cela il nous faut soit nous pencher sur le matériel soit sur le logiciel. Pour ce qui est du matériel nous ne voyons pas du tout comment faire ! Bien après ce suspens insoutenable, dévoilons cette subtile différence : le flag de déclenchement de l'interruption est automatiquement repassé à 0 pendant l'interruption. Nous avons de bonne raison de penser que cela se passe pendant le retour d'interruption. Pour notre pseudo timer, la remise à0 du flag se fait soit par une écriture dans le timer, soit par écriture d'un 1 dans le bit TOV0.
La subtile différence dont il est question ci-dessus a été corrigée en Juillet 2015. Ce sera donc le cas dans les ressources :
* arduinoMega8.zip
* l'ATMega16 d'un autre chapitre. Ceci est mis en œuvre dans le fichier io_timer.vhd.
Ces deux versions sont les plus à jour de ce point de vue.
Nous avons du mal à comprendre comment est architecturé FemtoOs en lisant son code. Nous allons donc essayer avec un autre.

### Atomthread

Les problèmes de portage sont identiques, mais il est plus simple de trouver l'interruption de commutation des tâches. Nous trouvons le code :
dans "atomport.c" qui nous montre que c’est l'interruption de comparaison du timer1 qui est utilisée. Régler le problème évoqué du flag peut se faire simplement en ajoutant une instruction après "atomIntExit(TRUE);" et avant l'accolade fermante. Mais, rappelons-le, ceci n'est plus nécessaire avec une version du processeur postérieure à Juillet 2015.

## Réaliser une commutation des tâches matérielle

Dans cette section, nous allons nous intéresser à une réalisation matérielle de la commutation des tâches. Il nous faut donc détailler ce qui est réalisé par le logiciel et l'implanter en VHDL. L'objectif est naturellement d'accélérer cette fameuse commutation.

## Système multi processeurs

Une autre possibilité de faire des tâches en parallèles et des les affecter chacune à un processeur. Il y a alors un certain nombre de problèmes à résoudre, en particulier sur la communication et synchronisation des processeurs entre eux.

### Le problème du logiciel

Le développement logiciel et matériel dans le cas de multi processeurs est un problème peu courant pour les petits microcontrôleurs : vous allez transformer les tâches en programmes et compiler chacun des programmes. Les programmeurs sont habitués à compiler des tâches ("thread" ou autres) au sein d'un seul programme. La mise au point est certes délicate, mais connue. Mais la mise au point de plusieurs programmes est certainement plus délicate. En ce qui nous concerne, nous développerons un exemple suffisamment simple pour ne pas être trop confronté à ce type de problème.

### Choix du processeur

Une contrainte forte pour nous est de programmer en langage C.
Le choix des processeurs n’est pas non plus indifférent. Si l’on se cantonne aux architectures 8 bits, il nous faut choisir une architecture relativement petite, sauf à prendre un FPGA très gros dont nous ne disposons pas pour nos essais. Il nous faudra donc abandonner notre architecture préférée des AVR ATMega trop gourmande en ressource FPGA. En restant dans le domaine des processeurs ATMEL, nous avons commencé à utiliser un ATTiny861 et aussi commencé à rédiger un chapitre de TP dans ce livre sur le sujet : cela fait de l'ATTiny861 un excellent candidat.
Les temps changent bien vite depuis l'écriture des quelques lignes ci-dessus, il y a deux ans. Aujourd’hui (Decempbre 2014) la Basys3 de Digilent est disponible pour le même prix que la Basys2 mais avec 15 fois plus de ressources FPGA. Faire du multiprocesseur même avec notre ATMega8 peut se réaliser sans problème. Nous ne l'avons pas encore réalisé pour le moment et sommes donc restés sur le choix de l'ATTiny861.

### Pour débroussailler

La réalisation de multiprocesseur nécessite la réalisation d'une interface de synchronisation entre les processeurs qu’il nous faut commencer par explorer. Le processeur que nous utilisons a implanté l'instruction IRET mais aucun mécanisme de déclenchement de l'interruption. Il nous est donc impossible d’utiliser celle-ci pour communiquer.
Il est facile d'imaginer la situation où l'un des deux processeurs (disons processeur A) demande à l'autre (processeur B) un travail quelconque et doit attendre que celui-ci soit effectué. Ce type de synchronisation peut être fait avec un bit particulier que le processeur A passe à 1 et attend que celui-ci passe à 0. C'est naturellement le processeur B qui le passera à 0.
Pour mettre cela en œuvre, nous allons commencer par un ensemble hyper simple, tellement simple qu’il n'y a en aucun cas lieu d'y mettre deux processeurs !

#### Processeur 1 pour compter et processeur2 pour afficher

Voici la partie matérielle sans commentaire :
Aller ici pour trouver le reste du code.
Voici les deux fichiers BMM nécessaire (un par processeur) :
* attiny_1_S6.bmm
ainsi que sa partie UCF associée :
* attiny_2_S6.bmm
* et le scipt pour compiler et charger.
Et maintenant le carburant pour les deux processeurs :
* programme pour processeur 1
* Programme pour processeur 2
On voit dans ce code qu’il n'y a aucune synchronisation : le processeur 2 ne fait que lire sans arrêt ce que le processeur 1 lui fourni et l'affiche.
Malgré le côté rudimentaire de ce que l’on vient de faire, on peut noter deux choses :
* il y a deux programmes C à écrire et donc à mettre au point. Quand ils sont simples comme ici, ce n’est pas un gros problème mais cela peut en devenir un sérieux pour une mise au point complexe.
* le côté identique des deux processeurs (on parle ici de leurs entrées/sorties) est plutôt embêtant si l’on veut gérer des activités complètement différentes. Ici on a laissé tombé l’affichage des dizaines par exemple (on aurait pu utiliser la sortie Led pour cela et ne pas la relier au processeur 1).
S'il l’on veut regrouper les deux programmes C en un seul, c’est certainement possible avec une mémoire programme sur deux ports, ce qui existe dans les FPGA depuis les Spartan 3. Mais il faut alors inventer un mécanisme pour faire démarrer le deuxième seulement sur un sous-programme ! La gestion correcte de la pile peut rendre ce mécanisme comme un vrai casse-tête ! De toute façon pour trois processeurs et plus, cette solution est inenvisageable.

### Projet futur

Nous envisageons de refaire le pacman avec :
* un tiny861 comme maître responsable de la gestion du fond du pacman : pac-gomme et scores.
* un tiny861 responsable du sprite pacman
* un tiny861 responsable du sprite fantôme 1
* un tiny861 responsable du sprite fantôme 2
Les trois esclaves pourront être rétrogradé en tiny461 si cela ne pose pas de problème du point de vue de la taille de code.