Activité : gestion d'interruptions

Nous allons à présent compléter notre système embarqué en ajoutant un périphérique qui effectuera des demandes d’interruption périodiques.

Compléter le projet Vivado

Si ce n’est pas déjà fait, ouvrez votre projet Computer dans Vivado :

cd $HOME/CoCiNum
./scripts/vivado vivado/Computer/Computer.xpr

Dans le panneau, Flow Navigator, exécutez l’action Add Sources, choisissez Add or create design sources et pressez le bouton Next.

Ajoutez les fichiers source VHDL suivants à votre projet. Tous ces fichiers sont situés dans des sous-dossiers de CoCiNum/src/vhdl.

Sous-dossier Fichier Rôle
Timer Timer.vhd Un périphérique pour compter le temps.

L’entité Timer

Un timer est un périphérique capable de compter le temps et de déclencher des interruptions périodiques. L’entité Timer est conçue comme un périphérique pour le processeur Virgule avec les ports suivants :

Port Direction Type Rôle
clk_i Entrée Logique Le signal d’horloge global
reset_i Entrée Logique La commande de réinitialisation
valid_i Entrée Logique Demande de transfert de donnée
ready_o Sortie Logique Indicateur de fin d’une lecture ou d’une écriture
address_i Entrée Logique Le bus d’adresses
write_i Entrée Vecteur de 4 bits Sélection des octets à écrire
wdata_i Entrée Vecteur de 32 bits Le bus de données en écriture
rdata_o Sortie Vecteur de 32 bits Le bus de données en lecture
evt_o Sortie Logique Indicateur d’événement

L’entrée address_i, sur un bit, permet d’accéder à deux registres de 32 bits :

address_i Registre Rôle
'0' limit_reg La valeur maximale du compteur.
'1' count_reg La valeur courante du compteur.

Le fonctionnement du timer est le suivant :

La sortie rdata_o est égale à :

Si limit_reg est non nul, la sortie evt_o vaut '1' à la fin de chaque cycle de comptage, c’est-à-dire lorsque count_reglimit_reg.

Si limit_reg vaut zéro, le timer ne compte pas et ne produit aucune demande d’interruption.

Modification du système

Paquetage Computer_pkg

Dans le fichier Computer_pkg.vhd, ajoutez des constantes pour définir les caractéristiques des nouveaux périphériques du système :

Constante Type Valeur Rôle
TIMER_ADDRESS Octet 83hex Les bits 31 à 24 de l’adresse pour accéder au timer.
INTC_EVENTS_TIMER Entier 2 Pour le contrôleur d’interruptions, le numéro de l’événement indiquant la fin d’un cycle de comptage du timer.

Entité Computer et architecture Structural

Dans le fichier Computer.vhd, dans l’architecture Structural, ajoutez une instance de l’entité Timer.

Nous ne fournissons pas de schéma pour cette étape. Vous pouvez prendre pour modèles les instances des périphériques UART et VInterruptController déjà présentes dans l’architecture.

Générer le bitstream et configurer le FPGA

Dans Vivado, générez le fichier binaire à charger dans le FPGA : Flow NavigatorProgram and DebugGenerate Bitstream.

Si ce n’est pas déjà fait, reliez le connecteur micro-USB de la carte à un port USB de votre PC et mettez la carte sous tension.

Connectez Vivado à votre carte Basys3 : Flow NavigatorProgram and DebugOpen Hardware ManagerOpen TargetAuto-connect.

Configurez le FPGA : Flow NavigatorProgram and DebugOpen Hardware ManagerProgram Device.

Compiler et exécuter un programme de démonstration

Nous allons adapter le programme Echo.c de manière à créer un nouveau programme de démonstration du timer Tick.c :

cd $HOME/CoCiNum/src/c
mkdir Tick
cp Echo/Echo.c Tick/Tick.c
cd Tick
gedit Tick.c &

Dans le fichier Tick.c, ajoutez des constantes pour définir les caractéristiques du timer :

// Définition des adresses des périphériques.
...
#define TIMER_ADDR 0x83000000

// Définition des registres des périphériques.
...
#define TIMER_LIMIT_REG TIMER_ADDR
#define TIMER_COUNT_REG (TIMER_ADDR + 4)

// Définition des masques pour détecter et acquitter les événements.
// masque = 2^(numéro de l'événement)
...
#define INTC_EVENTS_TIMER 4

// La fréquence d'horloge permettra de configurer le timer.
#define CLK_FREQUENCY_HZ 100e6

Ajoutez une fonction qui sera appelée à chaque demande d’interruption :

__attribute__((interrupt("machine")))
void irq_handler(void) {
    // Vérifier qu'il s'agit bien d'une interruption du timer.
    if (read32(INTC_EVENTS_REG) & INTC_EVENTS_TIMER) {
        // Signaler que l'événement a été traité.
        write32(INTC_EVENTS_REG, INTC_EVENTS_TIMER);
        // Afficher un message.
        UART_send_string("Tick! ");
    }
}

Modifiez le programme principal :

void main(void) {
    // Activer les interruptions du timer.
    write32(INTC_MASK_REG, INTC_EVENTS_TIMER);
    // Régler le cycle de comptage sur 1 seconde.
    write32(TIMER_LIMIT_REG, CLK_FREQUENCY_HZ - 1);
    // Attendre la réception d'un caractère par la liaison série.
    // Pendant ce temps, le timer demande des interruptions toutes les secondes.
    UART_send_string("Press a key to terminate the program.\n");
    while (!(read32(INTC_EVENTS_REG) & INTC_EVENTS_UART_RX));
    // Envoyer un message de fin.
    UART_send_string("\nBye!\n");
}

Compilez ce programme :

cd $HOME/CoCiNum/src/c/Tick
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -ffreestanding -nostdlib -T ../../../scripts/Virgule.ld -o Tick.elf ../../asm/Startup/Startup.s Tick.c
riscv64-unknown-elf-objcopy -O ihex Tick.elf Tick.hex

Si ce n’est pas déjà fait, ouvrez le logiciel GTKTerm pour communiquer avec l’ordinateur embarqué dans notre FPGA par liaison série sur USB :

cd $HOME/CoCiNum
./scripts/gtkterm

Dans le menu Configuration de GTKTerm, activez l’option CR LF auto.

Forcez un redémarrage du processeur en pressant le bouton-poussoir central de la carte Basys3.

La fenêtre GTKTerm doit à présent afficher le texte :

\\// This is the Virgule program loader.
\\// Send an hex file to execute or press ESC to switch into interactive mode.

Dans la fenêtre terminal de Linux qui vous a servi à lancer GTKTerm, exécutez la commande :

cat $HOME/CoCiNum/src/c/Tick/Tick.hex > /dev/ttyUSB1

Observez le résultat de l’exécution du programme. Dans GTKTerm, pressez une touche pour terminer.