Nous allons à présent compléter notre système embarqué en ajoutant un périphérique qui effectuera des demandes d’interruption périodiques.
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. |
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 :
reset_i
vaut '1'
, les registres limit_reg
et count_reg
sont mis à zéro.wdata_i
est copiée dans :
limit_reg
si address_i = '0'
,count_reg
si address_i = '1'
.count_reg
≥ limit_reg
, alors count_reg
revient à zéro,count_reg
< limit_reg
, alors count_reg
s’incrémente.La sortie rdata_o
est égale à :
limit_reg
lorsque address_i = '0'
count_reg
lorsque address_i = '1'
Si limit_reg
est non nul, la sortie evt_o
vaut '1'
à la fin de chaque
cycle de comptage, c’est-à-dire lorsque count_reg
≥ limit_reg
.
Si limit_reg
vaut zéro, le timer ne compte pas et ne produit aucune
demande d’interruption.
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. |
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.
Dans Vivado, générez le fichier binaire à charger dans le FPGA : Flow Navigator → Program and Debug → Generate 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 Navigator → Program and Debug → Open Hardware Manager → Open Target → Auto-connect.
Configurez le FPGA : Flow Navigator → Program and Debug → Open Hardware Manager → Program Device.
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.