vendredi 31 janvier 2014

xmos startKIT, signal vga avec tuile

Cet article fait suite au précédent. J'explique ici comment fonctionne le système des tuiles afin d'économiser la mémoire vidéo.

Le système des tuiles était très utilisé dans les années 80 alors que les ordinateurs ne possédaient que quelqes Ko de mémoire RAM. Une quantité insuffisante pour conserver un bitmap complet de l'affichage vidéo. Dans cette chronique j'utilise un générateur vidéo VGA 16 couleurs d'une résolution de 640x480. Ce qui nécessiterais 153600 octets de RAM pour conserver un bitmap complet de l'affichage vidéo. Par contre en utilisant une table de tuiles de 8x8 pixels et un buffer vidéo contenant seulement des index de type unsigned char, la table peut contenir 256 tuiles différentes. La mémoire vidéo utilise donc 640/8*480/8=4800 octets par buffer. Il y a 2 buffer vidéo, video_buffer et working_buffer. Si on utilise une table de tuiles complète (256 tuiles) on a besoin de 32x356=8192 octets supplémentaire. Donc au total 17792 octets sont utilisés par le générateur vidéo sur les 64Ko disponibles. Énorme gain par rapport à 2 buffers de type bitmaps: 307200 octets.

Ce court vidéo montre une animation de 8 balles qui rebondissent sur la bande blanche qui entoure l'écran ainsi que l'une contre l'autre. Chaque balle d'une couleur utilise 4 tuiles.

Système de tuiles

Le système de tuiles est en fait très simple c'est le principe des polices de caractères à points fixes, mais plutôt que de contenir des caractères, la table contient (elle peut contenir aussi des caractères) des figures réparties sur plusieurs tuiles. Il suffit d'afficher ces groupes de tuiles dans l'ordre approprié pour former la figure désirée. Si on prend l'exemple du démo ci-haut. Les balles sont de 16x16 pixels. Puisque chaque tuile ne contient que 8x8 pixels, il faut 4 tuiles pour dessiner chaque balle. La mémoire vidéo est organisée comme une mémoire de terminal texte, elle ne contient de le numéro des tuiles et le générateur vidéo utilise ces numéros pour indexer la table des tuiles qui elle contient les pixels à afficher. Utilisé comme terminal texte ce système pourrait afficher 60 lignes de 80 caractères 8x8 mais tous les caractères doivent-être de la même couleur et le fond uniforme de couleur unique. Cependant on pourrait modifier facilement le générateur vidéo pour avoir des caractères en inverse vidéo.

représentation d'objets graphiques

Pour afficher des objets graphiques il faut créer des groupes de tuiles qui agencés ensemble dans le buffer vidéo formerons l'objet désiré.

Par exemple, la balle rouge est construite avec les tuiles {16,17,18,19}. Pour dessiner une balle rouge à l'écran à la position {4,20} on place dans video_buffer l'information suivante:

video_buffer[4][20]=16; // ligne 4, colonne 20
video_buffer[4][21]=17; // ligne 4, colonne 21
video_buffer[5][20]=18; // ligne 5, colonne 20
video_buffer[5][21]=19; // ligne 5, colonne 21
Pour former l'image d'une balle il faut que l'ordre et la position relative des tuiles soit respecter. Donc si on veut déplacer la balle vers la droite de 10 positions et de 5 position vers le bas, on ajoute 10 à la coordonnée x et 5 à la coordonnée y de chacune des tuiles du groupe.

On sauve énormément de mémoire avec ce système mais il comporte aussi de nombreuses limitations. D'abord il faut utiliser un fond d'écran uniforme. Dans le démo le fond est jaune pâle et donc les pixels des tuiles qui entourent le dessin des balles doivent-être de la même couleur sinon on voit un rectangle autour des balles. Si le fond d'écran n'était pas uniforme il faudrait des tuiles différentes pour chaque couleur de fond.

Le deuxième problème est qu'on ne peut couvrir la surface complète avec 256 tuiles, il y a donc des répétitions. On le constate dans les jeux vidéo du début des années 80 alors que les fonds d'écrans sont fait de tuiles qui se répètent.

Si vous observez attentivement les jeux vidéo de ces années vous remarquerez que les parties de l'écran ou se déplace les objets sont de couleur uniforme. Les corridors du labyrinthe ou se déplace PACMAN est de couleur uniforme et tous les objets qui apparaissent dans le labyrinthe sont construits avec des tuiles dont le fond est de même couleur.

Il est possible de modifier dynamiquement le contenu d'une tuile pour modifier les pixels de fond en fonction de la position de l'objet sur l'écran mais ces objets doivent n'avoir qu'une seule instance sinon chaque instance doit utiliser son propre groupe de tuiles. Comme dans ce démo ou il y a 4 tuiles pour chaque couleur de balle. Si on voulait créer 1 balle de chacune des 16 couleurs disponible il faudrait un total de 64 tuiles. Il ne faut pas oublier qu'on ne peut créer qu'un nombre limité de tuiles. Si l'index est de type unsigned char comme dans ce démo on est limité à 256 tuiles. Si on augmente trop le nombres de tuiles on perd tout avantage en terme de gain mémoire.

code source

le générateur vidéo a été créé comme un projet module nommé module-vga16t afin d'être facilement réutilisable dans d'autre applications. Il s'agit d'une première version qui sera vraisemblablement modifiée.

fichier d'entête vga16t_gen.h

fichier vga16t_gen.xc

Fichier d'entête tuiles.h

Fichier tuiles.xc

Fichier bouncing-balls.xc

640pixels en 25,6µsec

l'intervalle de temps disponible en VGA pour sortir les 640 pixels est de 25,6µsec. Même avec un core logique clocké à 100Mhz il ne serait pas possible de faire ça en 'C' si ce n'était d'une fonctionnalité des xcore, c'est à dire le buffered port. Voici le code qui sort les pixels.

                int y = (line_cntr-23)/8;
                int r = (line_cntr-23) % 8;
                for (int x=0;x<SCREEN_WIDTH;x++){
                    int _8pixels=tuiles[video_buffer[y][x]][r];
                    rgb   <: _8pixels;
                }//for
Cette boucle for sort 1 ligne de 640 pixels. video_buffer contient les index des tuiles comme expiqué ci-haut. Ces index sont utilisés pour aller chercher l'informationn des pixels à afficher dans la table des tuiles. La table est organisé en 8 entiers de 32 bits par tuile. Chaque entier correspond à un segment de ligne de 8 pixels. En effet puisqu'on a besoin de 4 bits par pixel 32 bits peuvent contenir 8 pixels. La variable r indique quelle ligne de la tuile utiliser en correspondance avec la ligne vidéo courante. line_cntr est le numéro de ligne du frame vidéo (0-524). Puisque l'affichage commence à la ligne 23 de chaque frame on soustrait 23 pour avoir la bonne valeur de ligne dans buffer_video. Et puisque qu'une tuile s'étend sur 8 lignes on obtient r par (line_cntr-23) % 8. rgb est le port 4 bits qui envoie l'information pixel au moniteur. Comme vous le voyez on envoie l'entier au complet dans rgb car on a défini un buffered port de 32 bits pour rgb ainsi qu'un signal clock de 25Mhz.

Dans le fichier bouncing-balls.xc juste avant la fonction main on a les lignes:

out buffered port:32 rgb = XS1_PORT_4C;  // J7-5:J7-8

clock pixclk= XS1_CLKBLK_1;
rgb est donc buffered sur 32 bits et on défini un signal d'horloge appellé pixclk.
À l'intérieur de main() on a les lignes suivantes:
    configure_clock_rate(pixclk,XS1_TIMER_MHZ,4);
    configure_out_port(rgb,pixclk,0);
    start_clock(pixclk);
Pour le startKIT XS1_TIMER_MHZ vaut 100. 4 est un diviseur, donc le clock rate est de 100/4=25Mhz. On associe ensuite ce signal d'horloge au port rgb et finalement on démarre le signal. A partir de ce moment chaque fois qu'on envoie un entier de 32 bits vers rgb est il découpé en tranches de 4 bits à intervalle de 40 nsec sans que le core logique est besoin d'intervenir. Pendant ce temps la boucle for poursuit son exécution. Il n'y a donc aucun délais entre la envoie de chaque _8pixels.

Notez que les bits de poids faibles sortent en premier donc les bitmaps des tuiles est inversé comme dans un miroir (gauche-droite).


lien vers les sources des 2 projets:
projet bouncing-ball
projet module-vga16t

mercredi 22 janvier 2014

xmos startKIT, génération signal VGA

Je poursuis mes expérimentations avec le startKIT de XMOS. Après avoir expérimenter la génération d'un signal vidéo NTSC en noir et blanc, je suis passé en VGA 16 couleurs. Dans ce premier test il s'agissait simplement de générer les signaux nécessaires à la synchronisation verticale, horizontale et vidéo. La mémoire vidéo est constitué d'une seule ligne que le générateur répète 480 fois. Le maximum de pixels par ligne que j'ai pu obtenir en programmation 'C' est de 508 par ligne. Comme 1 octet du tableau video_buffer contient l'information de 2 pixels, ça donne un tableau de 254 octets.

Schématique

Schématique de l'interface entre la carte startKIT et le moniteur VGA.

image générée par le programme test

La ligne sombre est un artéfact causé par la photographie.

Code source

fichier d'entête du générateur vidéo.

générateur vidéo VGA 16 couleurs

programme test, barres de couleurs

Limitation mémoire

Pour créer un bitmap de 480 lignes de 504 pixels il faudrait 480*504/2=120960 octets. Réduisant la résolution à 240x254 on a encore besoin de 30480 octets. Pour réduire le besoin de mémoire, on peut réduire la résolution encore plus ou encore utiliser un système de tuiles. Dans le prochain article je vais expliquer ce qu'est le système de tuiles et l'utiliser dans un démo bouncing balls. Le système de tuiles était la méthode utilisée au début des années 80, lorsque la mémoire était coûteuse et rare. Mon VIC-20 avec ses 5Ko de RAM pouvait afficher des graphiques de 160×160 pixels en 16 couleurs en utilisant un système de tuiles. Pour utiliser le système de tuiles je dois modifier le générateur vidéo et je serai peut-être obligé de sacrifier des pixels horizontalement.

mise à jour 2014-01-23

La version de l'interface physique entre la carte startKIT et le moniteur VGA présenté en début cet article a l'avantage d'être simple mais l'inconvénient que les couleurs de code 9 à 14 sont délavées. J'ai donc conçue une version améliorée qui corrige ce problème.


Références:
vga sur Psoc
vga timing

lundi 20 janvier 2014

introduction aux interruptions

Dans cet article j'explique ce que sont les interruptions et leur utilisation.

Un programme est une suite d'instructions qui s'exécutent l'une à la suite de l'autre et qui éventuellement boucle au début dans un cycle sans fin. Exemple:

; clignotement d'un LED branché sur GP0
; code assembleur pour MCU PIC baseline
; GP0 configuré en sortie
main
   bsf GPIO, GP0
   call delay    
   bcf GPIO, GP0
   call delay
   goto main ; boucle infinie
Une interruption est un mécanisme par lequel le déroulement normal d'un programme est interrompu pour exécuter une autre programme de durée limitée, après quoi l'exécution du programme principal se poursuis à l'endroit ou il a été interrompu. Le programme exécuté lors de l'interruption s'appelle une routine de service d'interruption par la suite je vais utiliser l'acronyme anglophone ISR pour Interrupt Service Rroutine.

Quel est l'utilité des interruptions?

  1. Besoin de répondre aux événements en un temps déterminé. real time
  2. Libérer le processeur du besoin de sonder les périphériques pour voir s'il y a un événement. polling
Une application doit souvent répondre à des événements externes asynchrones, c'est à dire que ces événements peuvent se produire à n'importe quel point de l'exécution du programme principal. Une broche d'entrée change d'état et on doit réagir à ce changement dans un délais minimum. Si on n'a pas d'interruption comme c'est le cas des PIC baseline on est obligé de sonder la broche à un intervalle régulier suffisamment court pour répondre aux contraintes de temps de réponse. Ça complique le programme principal comme on l'a vu dans cet article. Non seulement ça complique la structure du programme principal mais le temps consacré à ces coups de sonde est du temps perdu lorsqu'il n'y a aucun événement.

À partir de cette constatation l'idée de l'interruption est apparu rapidement dans l'histoire des ordinateurs. Il y a donc à l'intérieur des microprocesseurs et microcontrôleurs des circuits dont la tâche est de surveiller les événements programmés pour générer une interruption. Lorsqu'un tel événement se produit le gestionnaire d'interruption déclenche le mécanisme qui va interrompre le programme en cours pour exécuter l'interruption.

Du plus simple au plus complexe

Le dans le cas le plus simple il n'y a qu'un seul vecteur d'interruption. C'est le cas des mcu PIC 8 bits midrange et enhenced midrange. l'ISR doit-être installé à l'adresse 0x04. Par contre beaucoup de processeurs disposent d'une ou plusieurs tables de vecteurs d'interruptions. Chaque entrée de la table contient l'adresse (ou un instruction de saut vers) le début de la routine d'interruption correspondant à la source (périphérique interne ou événement externe) de l'interruption. Par exemple les mcu Atmel AtTiny13a possèdent une table de 10 vecteurs situé en début de mémoire flash. La première entrée de cette table, à l'adresse 0, n'est pas un vecteur d'interruption c'est l'adresse de la routine d'initialisation. Les autres vecteurs correspondent à des périphériques ou des interruptions externes (changement d'état sur une broche).

L'avantage d'avoir un vecteur pour chaque interruption est qu'il n'est pas nécessaire de vérifier la source de l'interruption. Dans le cas des PIC si plusieurs interruptions sont activés, il faut au début de l'ISR vérifier quel est la source de l'interruption en lisant les indicateurs booléen (interrupt flag) associés à chaque source d'interruption. Non seulement ça exige du code supplémentaire mais ça rallonge le délais de réponse. Un autre avantage est la priorisation des interruptions.

exemple: table des vecteur pour l'atTiny13a

Address Labels Code Comments
0x0000 rjmp RESET ; Reset Handler
0x0001 rjmp EXT_INT0 ; IRQ0 Handler
0x0002 rjmp PCINT0 ; PCINT0 Handler
0x0003 rjmp TIM0_OVF ; Timer0 Overflow Handler
0x0004 rjmp EE_RDY ; EEPROM Ready Handler
0x0005 rjmp ANA_COMP ; Analog Comparator Handler
0x0006 rjmp TIM0_COMPA ; Timer0 CompareA Handler
0x0007 rjmp TIM0_COMPB ; Timer0 CompareB Handler
0x0008 rjmp WATCHDOG ; Watchdog Interrupt Handler
0x0009 rjmp ADC ; ADC Conversion Handler

Priorité et multi niveaux

S'il y a plusieurs sources d'interruption. Celles-ci peuvent avoir des priorités différentes. Les interruptions de haute priorité doivent-être servies avant celles de plus basse priorité. Mais si une interruption de basse priorité est en cours d'exécution au moment ou survient une interruption de plus haute priorité comment faut-il gérer ça? Dans bien des cas on ne peut se permettre d'attendre la fin de l'exécution de l'ISR courante, il faut que cette première interruption soit interrompue pour céder la place à celle de plus haute priorité. C'est ce qu'on appelle le multi-niveaux. L'exécution d'une ISR est interrompue et reprendra lorsque celle de priorité supérieure se terminera. Certains processeurs possèdent des mécanismes de priorisation des interruptions, c'est le programmeur qui choisi la priorité de chaque interruption. Sur les PIC24/dsPIC30/dsPIC33 et PIC32MX il y a des registres spéciaux pour configurer la priorité de chaque interruption. Si 2 interruptions de même priorité se produisent au même instant celle qui a le plus petit numéro de vecteur est servie en premier. Dans le cas des AtTiny13a, la priorité est fixé par le fabriquant. C'est l'ordre du vecteur dans la table qui détermine sa priorité. Le vecteur 1 a la plus haute priorité et le 10 la plus faible. Sur les PIC midrange et enhenced midrange il n'y a pas de priorité puisqu'il n'y a qu'un seul vecteur d'interruption.

Interruptions en file

Si une interruption de priorité plus basse ou égale se produit pendant l'exécution d'une interruption on peut réduire le temps de réponse de l'interruption suivante si on évite de restaurer l'état initial avant d'exécuter l'interruption suivante. Certains mcu possèdent un mécanisme pour faire ça. Si ce mécanisme n'existe pas il y a du temps perdu à restaurer les états qui seront resauvegardés par l'interruption qui est en file d'attente.

Interruption non masquable

Certains processeurs possèdent une interruption non masquable (NMI). Ce type d'interruption est habituellement utilisé pour signaler une interruption critique ou une erreur non récupérable du système. Elle est dite non masquable parce qu'elle ne peut-être désactivée ou bloquée par une autre interruption. Par exemple sur un système fonctionnant sur pile on pourrait utiliser un moniteur d'état de la pile qui déclenche une NMI lorsque le voltage descend sous un certain seuil permettant à l'ISR de sauvegarder les états du système en eeprom ou autre media permanent avant de mettre celui-ci en mode sommeil.

délais de réponse et variabilité

Le délais de réponse à une interruption est un facteur important à considérer dans une application. A partir du moment ou un événement se produit et le moment où l'ISR commence à servir cette interruption détermine le de réponse de l'interruption (latency). D'abords lorsqu'un interruption se produit il faut sauvegarder les états du programme en cours, c'est à dire le pointeur ordinal et les autres registres qui sont susceptibles d'être altérés par l'exécution de l'ISR. Bien sur ces états doivent-être restaurés avant de quitter l'ISR. C'est états sont sauvegardés généralement sur la pile des retours ou parfois dans des registres qui sont des doubles des registres originaux (shadow registers). Les PIC enhenced midrange sauvegarde le PC sur la pile et les registres WREG,STATUS,FSRx dans des copies en fin de mémoire RAM (bank 31). Pour les PIC midrange à l'exception du PC tous les autres états doivent-être sauvegardés par programmation au début de l'ISR ce qui rallonge le temps de réponse. En effet la sauvegarde/restauration des états ne peut-être considéré comme une réponse à l'interruption, ce n'est qu'un préambule.

La variabilité (jitter) est la variation du temps de réponse d'une interruption à l'autre. Habituellement un MCU termine l'exécution de l'instruction en cours avant de répondre à une interruption. Si les instructions n'ont pas toutes la même durée d'exécution il y aura une certaine variation dans le temps de réponse. Dans les applications temps réel l'ingénieur doit-être en mesure d'évaluer cette variabilité et s'assurer qu'elle ne déborde pas des seuils de tolérance de l'application.

Bonnes pratiques

Comme chaque interruption suspend l'exécution du programme principal, il est de bonne pratique de réduire le temps passé dans l'ISR. Plus une ISR est courte mieux c'est, surtout si elle se produit fréquemment. Une interruption de haute priorité devrait duré encore moins longtemps car elle bloque aussi la réponse aux interruptions de plus basse priorité.

Dans les applications qui requiert la plus faible consommation d'énergie il est de bonne pratique de construire entièrement l'application sur des interruptions. Après la phase d'initialisation le mcu est placé en mode basse consommation et se sont les interruptions qui le réactive. Lorsqu'une interruption a terminée son travail elle remet le mcu en basse énergie en quittant. Prenons l'exemple des odomètres de vélo qui fonctionnent sur une petite pile au lithium. l'odomètre tombe en sommeil 1 minute après avoir détecté la dernier événement. Lorsque le capteur sur roue détecte une rotation ou que l'utilisateur presse un bouton l'odomètre se réactive pour afficher les données.

jeudi 9 janvier 2014

se faire les dents sur XMOS startKIT

Il y a quelques semaines je présentais le startKIT de XMOS. Depuis j'ai lu pas mal de documentation et cette semaine je me suis fait les dents sur le startKIT en écrivant ma première application. Ça n'a pas été facile de m'adapter aux contraintes du langage XC mais avec de la persévérance j'y suis finalement arrivé.

le jeux de la vie

Le jeux de la vie est un automate cellulaire sur 2 dimensions imaginé par le mathématicien John H. Conway en 1970. Soit une grille ou chaque carré (cellule) peut prendre 2 couleurs, disons noir ou blanc. A chacune des cellules de la grille on applique un ensemble de règles simples et on conserve le nouvel état de la grille et recommence la même opération à partir de ce nouvel état. Lorsque Conway a imaginé cet automate les ordinateurs personnels n'existaient pas encore, chaque itération était calculé à la main et dessiné sur du papier quadrillé. Heureusement on n'en est plus là. C'est beaucoup plus intéressant de confier ce travail à un ordinateur et d'afficher le résultat de chaque itération à l'écran.

J'ai choisi le jeux de la vie pour me faire les dents sur le startKIT car c'est une programmation simple, du moins lorsque c'est écris en 'C' en single thread. Lorsqu'on doit apprendre une nouvelle façon de faire c'est plus difficile comme je l'ai constaté.

La grille est toroïdale et de dimension 36x26. Par toroïdale on entends ceci. Imaginez que l'écran de l'ordinateur est une feuille de papier et qu'on le pli en cylindre pour que le haut vienne touché au bas et qu'ensuite on étire ce cylindre pour que le 2 bouts se touchent. On obtient ainsi une tore et les cellules sont à la surface de cette tore. L'univers ainsi créé est sans rebords mais de surface limité. Ainsi lorsqu'un glider se déplace sur la grille, au lieu de disparaître lorsqu'il arrive à un rebord de l'écran il réapparait de l'autre côté.

Voici un vidéo de la simulation en action. Sur la ligne du bas s'affiche le nombre d'itérations.

Le calcul de chaque itération étant trop rapide j'ai ajouter une délais 0,5 seconde entre chaque itération.

schématique

Code source

Description

Le tableau video_buffer est la mémoire vidéo qui contient l'information à afficher à l'écran. Cette mémoire contient les indices des caractères dans la table font. life_buffer contient l'état de l'univers de l'automate cellulaire. Ce tableau est double car lors du calcul d'une itération la version originale ne peut-être modifiée. On écris donc le résultat dans l'autre moitié et on alterne entre les deux moitiés à chaque itération.

Dans la procédure principale on initialise la grille life_buffer en utilisant le générateur pseudo-hasard rand(). La variable timer t n'est utilisée que pour initialiser le générateur pseudo-hasard.

Le générateur de signal NTSC utilise 3 ports de 1 bit, ntsc_sync_port génère le signal de synchronisation. ntsc_blvl_port détermine le niveau noir et finalement ntsc_video_port est la sortie des pixels vidéo.

interface

Dans le langage XC une interface permet de définir un canal de communication entre 2 threads. Dans ce programme il y a 2 threads next_gen et ntsc_video. Chacun de ces threads s'exécute sur un core différent. Comme la variable video_buffer et contrôlée par le thread ntsc_video le thread next_gen ne peut y accéder. Donc lorsque next_gen a terminé le calcul d'une itération il envoie le nouveau tableau au thread ntsc_video par l'interface cells_array. Cette interface ne contient qu'une seule fonction CopyCellArray(). le nouveau tableau est copié dans video_buffer pendant le vertical retrace donc il n'y a pas de glitches dans l'affichage.

Dans la fonction ntsc_video() il y a un select. Les select en XC servent à écouter un canal de communication en attente d'information. Pour chaque fonction de l'interface il doit y avoir un case correspondant dans le select. Chaque case doit-être terminé par un break ou un return. Pour éviter que le thread ne bloque sur l'attente du message fournis par CopyCellArray() on utilise un

default:break;
. Le default permet de sortie du select et continuer l'exécution du thread. S'il n'y avait pas de cette option le générateur NTSC ne fonctionnerais pas.

Une interface est un canal client/serveur. C'est à dire qu'un des thread est le serveur c'est à dire celui qui attends les requêtes distribuées via les fonctions de l'interface. Le client lui évidemment envoie les requêtes.

Dans la fonction main(),

interface cells_array ca;
déclare une variable d'interface ca qui est passé en arguments aux 2 threads qui utilisent cette interface. Notez comment le paramètre d'interface est déclaré dans chaque thread. client pour next_gen() et server pour ntsc_video().
void next_gen(client interface cells_array ca) 
void ntsc_video(out port sync_port,
                out port blvl_port,
                out port video_port,
                server interface cells_array cells) 
Les interfaces sont des canaux de communications point à point donc la variable ca ne peut-être passée en argument qu'à un seul thread client et un seul thread server. Si un serveur a plusieurs clients il faut déclarer une variable différente pour chaque client. l'ensemble de ces variables peut-être gardé dans un tableau qui est passé en argument au serveur.

Conclusion

Bien que de se familiariser avec une nouvelle technologie demande des efforts le succès est toujours gratifiant. La version 0.2 de ce programme pourrait utiliser les 2 règles tactiles et le bouton qui sont sur la carte startKIT pour créer une interface utilisateur servant à initialiser la grille au lieu de le faire avec une fonction au hasard. A suivre.


Mise à jour 2014-01-18

game of life version 0.2

  • Initialisation manuelle de la grille en utilisant le bouton, slider x, slider y qui sont sur la carte startKIT.
  • augmentation du nombre de cellules, grille de 176x110.

Au démarrage le mot INITIALISATION apparaît au bas de l'écran et un pixel clignote au centre (curseur). En utilisant les 2 slider on déplace le curseur sur la grille. A l'endroit désiré on presse le bouton pour mettre une cellule à cette endroit. S'il y a déjà une cellule elle est effacée (inverse l'état sous le curseur). Pour sortir de l'initialisation il faut garder le bouton enfoncé 2 secondes.

Dans ce vidéo la configuration initiale est un U constitué de 7 cellules comme indiqué sur la page jeux_de_la_vie sur wikipekdia. On aperçoit donc la face de clown à l'itération 110.

Code source

`

J'ai réorganisé le code source en plusieurs fichiers, de plus ce programme utilise 2 modules fournis par XMOS, module_startkit_gpio et module_capacitive_sensing

Le projet peut-être téléchargé ici.

Difficulté avec l'IDE

Lorsqu'on utilise des modules dans un projet il ne suffit pas de les référencés dans les propriétés du projet comme ici.

Ça ne suffit pas pour que le compilateur/lieur trouve le code source. Il faut indiquer les modules dans le Makefile.

Dans le project explorer on double-clique sur le Makefile pour ouvrir une fenêtre de configuration du Makefile.

Dans le groupe Used Modules on coche les modules utilisés par le projet.

J'ai du chercher avant de trouver la solution. Si on référence un module dans les propriétés du projet il me semble que l'IDE devrait configuré le Makefile en conséquence.


Si les automates cellulaires vous intéressent Golly est un excellent logiciel de simulation multiplateforme. Il ne simule pas seulement le jeux de la vie mais de nombreux autres types d'automates 1D ou 2D. Vous pouvez-même créer vos propres règles. Il y a de nombreux exemples de configurations qui donnent des animations intéressantes.

vendredi 3 janvier 2014

C'est parti!

Dans mon article de présentation du startKIT j'écrivais que les gros joueurs du marché MCU ne tarderaient surement pas à réagir à l'émergence de XMOS sur le marché en ajoutant des multi coeurs fonctionnant dans les centaines de Mhz à leur ligne de produits. L'année 2014 commence avec TI qui annonce des dual cores fonctionnant à 200Mhz, capable de 400MIPS. Les autres ne devraient pas être long à suivre.

On voit de plus en plus dans les automobiles des ordinateurs de bord avec affichage graphique LCD. Il y a donc une demande pour des processeurs plus puissants. En augmentant la puissance des microcontrôleurs et en leur donnant le multi coeurs et des gestionnaires de mémoire le nombre de MCU utilisés dans une automobile va diminuer. Au lieu d'avoir un MCU pour chaque système, freins ABS, contrôle d'admission, appareillages électriques, GPS, etc. Il deviendra possible d'intégrer toutes les fonctions dans une seule unité centrale. Les manufacturiers automobile utilisent déjà des systèmes d'exploitation temps réel comme QNX car pour ce types d'applications il faut un noyaux système temp réel. Les noyaux comme Linux ne sont pas appropriés.

Début novembre 2008 le Dr Tanembaum, inventeur du système micro-noyau MINIX, annonçait la réception d'une subvention de 2,5M d'euros pour avancer le développement de Minix 3. Le système Minix 3 cible particulièrement les systèmes embarqués (Embedded). Il y une convergence entre les systèmes d'exploitations comme Minix et l'utilisation de MCU de plus en plus puissant dans l'automobile et ailleurs.

mercredi 1 janvier 2014

xcore, rien de nouveau sous le soleil

Ce matin en suivant des liens je me suis retrouvé à lire cet article sur wikipedia. Voici un paragraphe extrait de cet article.

Each PP included its own memory of 4096 12-bit words. This memory served for both for I/O buffering and program storage, but the execution units were shared by 10 PPs, in a configuration called the Barrel and slot. This meant that the execution units (the "slot") would execute one instruction cycle from the first PP, then one instruction cycle from the second PP, etc. in a round robin fashion. This was done both to reduce costs, and because access to CP memory required 10 PP clock cycles: when a PP accesses CP memory, the data is available next time the PP receives its slot time.

En gros les PP (Peripheral Processor) sont des CPU qui gérait les E/S sur les CDC-6600. N'est-il pas frappant de voir que cet ordinateur des années 60s utilisait le même mécanisme que les xcores. C'est à dire un ensemble de registres qui maintient les états et qui partagent la même unité centrale. C'est le multi-threading des xcore discuté dans mon article précédent.

En fait la lecture de cet article nous révèle qu'il n'y a vraiment rien de nouveau sous le soleil en ce qui concerne les CPU. Le CPU du CDC-6600 datant de 1965 (presque 50 ans) avait déjà une architecture RISC avec register file. 8 registres de 60 bits pour les opérandes, 8 registres de 18 bits pour les adresses et 8 registres de 18 bits pour les incrémenteur (adressage relatif et branchements). Les CDC-7600 eu possédait une technologie très semblable au superscalaire d'Intel, c'est à dire que plusieurs instructions pouvaient être exécutées en parallèle. Son instruction stack était l'équivalent de ce qu'on appelle aujourd'hui un pipeline.

Puisque tout ce qu'on retrouve dans les CPU d'aujourd'hui existait déjà il y a 50 ans, alors qu'est-ce qui à progressé? Essentiellement la technologie des circuits intégrés. Un CDC-6600 qui était une grosse armoire nécessitant un système de refroidissement au fréon aujourd'hui tiendrais sur une petit morceau de silicium et couterait 1 ou 2$.

Bien sur au niveau des détails il y a eu quelques améliorations mais l'essentiel était déjà là. Je n'oserai plus utiliser le qualificatif de moderne pour qualifier les technologies xcore, propeller, hyper-threading, superscalar etc. La seule chose qui est moderne est notre capacité à intégrer sur des échelles de plus en plus microscopique. Certains processeurs sont maintenant gravés en 22 nanomètre, alors que le CDC-6600 était fabriqué avec des transistors bi-jonctions au silicium. C'était le premier ordinateur à utiliser des transistors au silicium et avec son clock à 10Mhz c'était l'ordinateur le plus puissant sur le marché. Un programme écris en FORTRAN pouvait exécuté 0,5 MFLOPS, le super-ordinateur de son époque.