Environnement logiciel sur FPGA - Partie 2 : Mise en place du noyau logiciel

Publié dans: 

Dans cette deuxième partie, nous décrirons l’environnement logiciel que nous avons créé pour le System-on-Chip. Nous présenterons aussi rapidement l’API contenant les fonctions en langage C.

 

Création de la routine d’interruption (ISR) en assembleur MIPS

 

Le processeur MIPS R2000 ne gère pas la priorité des demandes d’interruption. Il nous a donc fallu implémenter cette partie du traitement des interruptions au niveau logiciel. L’implémentation de la ISR (Interrupt Service Routine) a été réalisée en assembleur et le traitement des interruptions fait appel à une fonction écrite en langage C.

 

Le design du processeur softcore MIPS veut que l’ISR soit implémentée à l’adresse 0x3c de la ROM. Nous avons donc dû modifier le programme d’amorçage en langage assembleur (start.S, cf. Partie 1) pour que le système réagisse en présence des interruptions et cela, en sauvegardant l’état des registres en cours d’utilisation dans un vecteur de registres appelé vecteur d’interruption.

 

Ce programme renvoie dans ce cas à une procédure en C que nous avons appelée OS_ISR. Le contexte doit ensuite être restauré avant la reprise de son exécution. L’interruption est ainsi totalement transparente. Il fallait positionner ce code à l’adresse mémoire exacte 0x3c pour activer le vecteur d’interruptions (au 60ème octet exactement =⇒ 15ème instruction). Il fallait également différencier les interruptions issues des périphériques de celles générées par la commande syscall en modifiant l’implémentation du processeur softcore [Wik01].

 

La sauvegarde effective des registres du CPU commence à l’adresse 0x4c pour le cas des interruptions matérielles tandis que le traitement de la commande syscall débute à l’adresse 0x3c.

 

Extrait du fichier start.S
 

Isr_software : # adresse 0x3c - entrée interruption logicielle
.set noat
nop
move $27, $0
b_isr_core
nop
isr_hardware : # adresse 0x4c - entrée interruption matérielle
addi $27, $0, -4
isr_core :
addi $29, $29, -144 # réajustement de la position du pointeur sp
sw $1, 16($29) # sauvegarde du registre at
sw $2, 20($29) # sauvegarde du registre v0
sw $3, 24($29) # sauvegarde du registre v1
sw $4, 28($29) # sauvegarde du registre a0
sw $5, 32($29) # sauvegarde du registre a1
sw $6, 36($29) # sauvegarde du registre a2
...
lw $4, 0x1000($6) # adresse de IRQ STATUS
lw $5, 0x1100($6) # adresse de IRQ MASK
and $4, $4, $5
addi $5, $29, 0
jal OS_ISR # jump `a la procedure
lw $1, 16($29) # at - Restauration du contexte
lw $2, 20($29) # v0
lw $3, 24($29) # v1
lw $4, 28($29) # a0
...
addi $29, $29, 144 # réajustement de la position de sp
isr return :
mtc0 $27, $12 # restauration des interruptions
jr $26 # retour au programme interrompu

 

Traitement des interruptions en langage C

 

Lors d’une interruption matérielle, l’environnement exécute désormais une fonction OS_ISR que nous avons implémentée dans un des fichiers en langage C du projet. Cette procédure décrit la routine d’interruption logicielle que le système doit appliquer.

 

En retrouvant l’adresse contenant les données du registre irq_status (qui contient les états des interruptions systèmes), nous pouvons réguler le comportement du système face aux différents types d’interruptions possibles.

 

En résumé, voici les grandes étapes de traitement lors d’une interruption :
1. le processeur sauve l’état courant du processeur, puis effectue un saut vers l’adresse de la routine d’interruption.
2. la routine d’interruption sauve éventuellement le reste des registres.
3. la routine d’interruption détermine l’identité du périphérique interrompant le processeur.
4. la routine d’interruption sert le périphérique.
5. la routine d’interruption remet à jour l’état du processeur sauvé avant le service de l’interruption, puis redonne la main au programme interrompu.

 

Extrait de la fonction OS_ISR
 

void * OS_ISR(unsigned int status, int is_isr, void *sp)
{
  if (is_isr)
  {
     if (status & 0x02)
     {
        /* Traitement de l’interruption UART */
     }
     else if (status & 0x01)
     {
     /* Traitement de l’interruption timer */
     }
     else ...
  }
  else
     syscall process(sp);

  return sp;
}

 

Fonctions de gestion des ISRs

 

Afin de faciliter le traitement des interruptions, nous avons développé une couche d’abstraction qui permet d’installer et d’enlever des routines ISR en runtime. Un système de multiplexage de ces routines a été installé dans OS_ISR ce qui décharge le développeur des manipulations bas-niveau des détails techniques des ISRs.

 

int isr install(int mask, void (*proc)())
Installe une routine d’interruption proc spécifique à un périphérique donné en utilisant le masque mask.

 

void isr uninstall(void (*proc)())
Enlève une routine d’interruption proc préalablement installé.

 

Fonctions de gestion des sections critiques

 

void critical start()
Entre dans une section critique, pendant laquelle toute demande d’interruption est ignorée.
Les sections critiques peuvent être imbriquées (nested)

 

critical end()
Sort d’une section critique.

 

Fonctions d’affichage

 

void screen mode set(int mode)
Sélectionne le mode d’affichage. Les modes texte et RLE correspondent respectivement aux valeurs 0x0 et 0x1.

 

void screen_text_scroll()
Assure le défilement de l’écran (screen scroll) en mode texte.

 

void screen_text_locate(unsigned int x, unsigned int y)
Positionne le curseur de texte `a la ligne y et `a la colonne x.

 

void screen_text_write char(char c)
Écrit un caractère sur l‘écran en mode texte en prenant en compte la position du curseur de texte.

 

void screen_text_color_set(unsigned int fg, unsigned int bg)
Permet de sélectionner la couleur de l’arrière-plan (background) et du premier plan (foreground) en mode texte.

 

void screen_text_clear()
Efface l’écran en mode texte.

 

void screen_rle_base_set(unsigned int addr, unsigned int len)
Spécifie l’adresse de départ et la longueur des données d’une image compressée en RLE.

 

Fonctions de gestion des périphériques PS/2

 

void ps2_init(int dev)
Initialise le périphérique PS/2 correspondant à la valeur dev (0x0 pour le clavier, 0x1 pour la souris).

 

int ps2_read(int dev)
Lit une donnée à partir du périphérique PS/2 sélectionné.

 

void ps2_write(int dev, char *s, unsigned int len)
Envoie une donnée au périphérique PS/2 sélectionné.

 

int keyboard_read(struct keyboard event *ev, int timeout ms)
Lit un évènement issu du clavier.

 

void keyboard_leds_set(int status)
Spécifie l’état des LEDS du clavier.

 

int keyboard_leds_get()
Renvoie l’état des LEDS du clavier.

 

void keyboard_table_set(struct keyboard table *table)
Permet de générer la table de traduction du Scan Code du clavier en code ASCII.

 

void mouse_position_set(int x, int y)
Spécifie la position du curseur de la souris.

 

int mouse_read(struct mouse event *ev, int timeout ms)
Lit un évènement issu de la souris.

 

Fonctions de génération de son

 

int sound_init()
Initialise le système de génération sonore et installe un gestionnaire d’interruption sur le
timer pour assurer la restitution des notes musicales en arrière-plan.

 

int sound_pause(int flush)
Arrête immédiatement toute génération de son. Si flush a une valeur différente de 0, la partition musicale en cours est effacée.

 

void sound_play(unsigned int freq, int duration ms)
Génère un signal sonore de fréquence freq pendant duration_ms millisecondes. De plus, la génération des notes musicale est continuée si une partition musicale a été mise en pause au préalable.

 

void sound_volume_set(int volume)
Permet de contrôler le volume du son. volume peut avoir une valeur entre 0 et 7.

 

static char * sound_music_play(char *s)
Démarre la génération en arrière-plan de notes musicales conformément au contenu de la chaîne de caractères pointée par s. Cette chaîne doit contenir une partition musicale au format MML (Music Macro Language). Le langage de description MML a été introduit dans les premières versions de Microsoft BASIC. Il était couramment utilisé dans les années 1980 et notamment dans les jeux vidéo.
Une partition MML est une succession de caractères respectant la syntaxe suivante [Jas01] :

cdefgab - lettres qui correspondent aux notes musicales. Leur durée peut être altérée par un chiffre qui suit la lettre. Un caractère + ou # indique une dièse. - indique un bémol. Ainsi e-4 correspond à la note Mib jouée pendant un quart de mesure.

r - traduit par un Silence, sa durée est décrite de la même manière que celle des notes.
o - sélectionne l’octave dans laquelle les notes qui suivent seront jouées. Par exemple, o6 correspond `a la sixième octave.
>, < - sont utilisés pour monter ou descendre d’une octave.
l - suivi d’un chiffre, définit la durée par défaut des notes qui suivent.
v - suivi d’un chiffre, spécifie le volume du son.
t - suivi d’un chiffre, spécifie le tempo.

 

Fonctions de la connexion série RS232

 

void uart_init(int baud)
Initialise la UART pour une transmission à baud bits/seconde de données sur 8 bit avec 1 bit de stop et aucun contrôle de flux.

 

void uart_write(char *s, unsigned int len)
Transmet à l’UART en arrière-plan un bloc de données de longueur len et commençant à l’adresse s.

 

int uart_read(char *s, unsigned int max len, int timeout)
Lit une donnée reçue par l’UART.

 

Fonctions de la connexion Ethernet

 

int eth_init()
Initialise le contrôleur Ethernet MAC et installe les gestionnaires d’interruption associés.

 

int eth_frame_recv(char *frame, unsigned int max length)
Reçoit un frame Ethernet dans l’adresse mémoire frame.

 

int eth_frame_send(char *frame, unsigned int length)
Démarre une transmission en arrière-plan d’un frame Ethernet à l’adresse frame et de longueur
length octets.

 

void eth_frame_status()
Retourne l’état de la dernière opération (succès, en cours, ou échec).

 

Applications possibles

 

En utilisant cette API, nous avons pu tester et vérifier le fonctionnement de toutes les parties du système. Nous avons en effet suivi l’exécution des programmes que nous avons écrit sur un moniteur via le port VGA et interagit avec le système via les périphériques PS/2 (clavier et souris).

Grâce à cet environnement de développement, nous avons pu ainsi développer et tester plusieurs applications telles que :
• un Packet Sniffer assurant la réception et l’envoi de trames via la connexion Ethernet ;
• un Mini-terminal pour le test de la liaison série via le port RS232 ;
• un Visualiseur d’images avec la décompression puis l’affichage d’images au format RLE ;
• un Synthétiseur sonore en utilisant le système audio implémente.

 

 

Références :

[Wik01] : http://fr.wikipedia.org/wiki/Processeur_softcore
[Jas01] : JASON SCOTT. ANSI Music - The Technical Details , 2008

 

Blog Tags: