Intégration d’un esclave Avalon dans Platform Designer

Tarik Graba

2022-2024

Le protocole Avalon

La figure suivante, montre des transactions simple d’écriture puis de lecture utilisant le protocole Avalon.

Transactions d’écriture:

Transactions de lecture:

Les signaux read et write peuvent passer à 1 dans le même cycle. Dans ce cas, nous avons des transactions de lecture et d’écriture simultanées.

Si ni read ni write sont à 1, l’interface est inactive (IDLE).

Un simple registre Avalon

L’un des avantages de protocole Avalon (au moins de la séparation des signaux read et write) est qu’il est trivial d’associer à ses signaux de contrôle les signaux de contrôle d’un registre (sans logique supplémentaire).

La figure suivante représente donc une cible (Agent) tout à fait valide.

Comme les signaux waitrequest et read sont optionnels, nous pouvons tout simplement associer le signal write à l’entrée Enable qui valide l’écriture dans le registre.

Nous avons donc la correspondance suivante:

Comme un registre peut être mis à jour en un cycle quand son signal d’activation (EN) est haut et sa sortie est valide à chaque cycle, le signal waitrequest peut être omis (ce qui revient à le mettre systématiquement à 0). Le signal read peut aussi être omis, car les données sont systématiquement disponibles à la sortie du registre (ce qui revient à le mettre systématiquement à 1)

Description RTL su registre

Nous allons décrire un registre qui sera connecté aux sorties LEDs de la carte. Ce registre replacera le module PIO utilisé dans plateforme précédemment construite.

Ce fichier contiendra un module du même nom (simple_mm_register). Avoir le même nom pour le module n’est pas une obligation, juste une bonne pratique.

Interface du module RTL

L’interface du module devra être comme suit:

module  simple_mm_register(
  input  clk,
  input  reset_n,
  input  avs_write,
  input  [31:0] avs_writedata,
  output [31:0] avs_readdata,
  output [9:0]  leds

);

// Ajoutez à partir de là la description RTL

endmodule

La description RTL

Le registre

Bien que seulement 10 bits soient utilisés, nous vous demandons d’écrire le code d’un registre 32 bits. Au reset les 32 bits seront forcés à la valeur 0.

Si le signal write est actif, la valeur du registre sera chargée à partir de l’entrée writedata.

La description du comportement devrait ressembler au code suivant:

logic[31:0] R;

always_ff @(posedge clk or negedge reset_n)
  if(!reset_n)
    R <= '0;
  else if(avs_write)
      R <= avs_writedata;

Connexion de la sortie

La sortie vers l’interface Avalon (readdata) doit être connectée à la sortie du registre. Ceci permettra de relire la valeur contenue dans le registre (par le processeur par exemple ou durant le test).

Les 10 bits de poids faible seront aussi connectés à la sortie leds pour permettre de les visualiser sur la maquette.

Le code de connexion devrait ressembler au code suivant:

assign avs_readdata = R;
assign leds  = R[9:0];

Intégration dans Platform Designer

Platform Designer permet de générer automatiquement l’interconnect de notre système. Pour cela, il a besoin de connaitre le protocole utilisé par notre module ainsi que l’association entre les signaux de ce protocole est les signaux de l’interface de notre module RTL. Donc, pour pouvoir intégrer le module, il faut lui fournir des informations supplémentaires.

Ajout d’un nouveau composant (utilisation de l’assistant)

Dans le panneau IP Catalog vous pouvez cliquer sur new component. Un assistant vous guidera pour ajouter votre module RTL à la liste des modules disponibles.

Il faut choisir un nom ainsi qu’un nom d’affichage (name et display name . Le nom ne doit pas contenir d’espaces ou de caractères spéciaux.

Ensuite, vous pouvez associer vos sources RTL. En cliquant sur Analyse Synthesis Files l’outil analysera vos fichiers et tentera de “deviner” le protocole utilisé par votre module en se basant sur les noms des signaux de son interface. Au-delà du protocole Avalon, Platform Designer supporte d’autres protocoles de communication sur puce comme AXI par exemple. Aussi, il peut identifier les paramètres d’un module pour les exposer dans l’interface de l’outil.

Après cette étape, vous aurez éventuellement des erreurs (par exemple, il se peut que l’assistant associe la sortie leds à un second signal readdata). Nous corrigerons ces erreurs dans les étapes suivantes.

Vous pouvez passer directement à l’onglet Signals & Interface. Supprimer le signal leds de l’interface avalon_slave et ajouter une interface de type conduit. Ce type d’interface permet d’indiquer que nous avons juste un ensemble d’entrées/sorties et nous ne voulons pas que Platform Designer leur donne un sens particulier.

Ajoutez le signal leds comme une sortie sur 10 bits de ce conduit (comme le montre la figure suivante).

Ici le nom du signal DOIT correspondre au nom du signal dans le module RTL

Une fois complété, vous pouvez appuyer sur Finish. Le nouveau composant devrait apparaitre dans la liste des composants utilisable dans le panneau IP Catalog.

Note

L’assistant de création de composant génère un script TCL (xxxx_hw.tcl) associé au composant. Platform Designer analysera systématiquement tous les fichiers avec suffixe _hw.tcl dans le répertoire du projet (ainsi que dans une liste de répertoire correspondant à l’installation de l’outil et les préférences de l’utilisateur).

Ce fichier contient la description du module (au sens Platform Designer), la liste des fichiers RTL, des paramètres et la description des différentes interfaces.

La façon de construire ces scripts est documentée dans la documentation officielle de Platform Designer. Il est donc possible de construire ce fichier de description directement, sans passer par l’assistant d’ajout de modules.

Intégration et tests

Supprimez le module PIO précédemment connecté aux LEDs et remplacez-le par le nouveau composant que vous venez d’ajouter à la bibliothèque des composants. Le système mis à jour devrait ressembler à la figure suivante:

Une fois fait, vous pouvez régénérer le RTL de la plateforme, refaire la synthèse dans Quartus II et tester le comportement sur la carte.

N’oubliez pas de modifier le programme en C de test pour prendre en compte vos modifications.

Module GPIO programmable

Nous allons faire évoluer notre module GPIO vers un contrôleur programmable d’entrées sorties.

Il devra permettre les fonctionnalités suivantes:

Le schéma suivant montre l’architecture globale de ce contrôleur.

Les entrées/sorties (pio_i/pio_o) seront sur 32 bits et permettront de se connecter au monde exterieur (les leds et interrupteurs par exemple) Ces signaux seront connectés en interne à deux registres (data_in/data_out).

D’un point de vue logiciel, nous auront accès aux registres 32 bits suivants:

adresse nom taille fonction
0 data 32 bits en écriture modifie le registre de sortie (data_out)
en lecture récupère la valeur du registre d’entrée (data_in)
4 enable 32 bits chaque bit permet de dire si la pin correspondante est utilisée
en lecture on peut lire son état
8 irq mask 32 bits pour chaque pin (en entrée), si le bit correspondant est à 1, une interruption peut être générée
en lecture on peut lire son état
12 irq pol 32 bits pour chaque pin (en entrée), si le bit correspondant est à 1, l’interruption est générée sur niveau bas
en lecture on peut lire son état
16 irq ack 32 bits une écriture (quelle que soit la valeur) remet à zéro la sortie d’interruption
en lecture aucun effet

Note

Création d’un nouveau module

En partant du module précédent, créer un nouveau module prog_gpio. Vous pouvez aussi copier, puis, modifier le fichier de description (_hw.tcl) pour qu’il soit reconnu par Platform Designer.

Comme ici nous avons besoin d’adresser plusieurs registres, il faut ajouter une entrée pour l’adresse venant du bus Avalon. Si on respecte la convention précédente, on peut la nommer avs_address. Cette nouvelle entrée doit être suffisamment grande pour représenter la plage d’adresses du contrôleur (de 0 à 16 inclue).

Ajouter aussi les entrées/sorties suivantes:

Modification de la description de l’IP sur Platform Designer

  1. Paramètres de l’interface Avalon:

Nous voulons des adresses d’octets, vérifiez que dans la configuration de l’interface esclave, les adresses sont bien des adresses d’octets (Address Units : SYMBOLS et Bits per symbol : 8). Cette information est utilisée à la génération de l’interconnect pour ne transmettre que les bits d’adresse nécessaires.

Note nous aurions pu ici numéroter les registres 0,1,2,3 et 4 et indiquer que nous voulons une adresse mot (WORD). Dans ce cas, la correspondance avec les adresses vues par le logiciel ne seraient pas directes.

Notez qu’il est possible de donner explicitement la plage d’adresse utilisée par le périphérique (Explicite Adress Span), sinon, elle est déduite de la taille du bus adresse.

  1. Associez l’entrée d’adresse à l’interface Avalon.
  2. Modifiez les conduits pour y associer vos signaux pio_*.

Ici, vous pouvez prévoir deux conduits séparés ou un conduit unique permettant d’exporter les deux signaux. Attention aussi à la direction des signaux.

  1. Ajoutez un Interrupt Sender et associez-le à votre sortie irq.

Vérifiez que le fichier source (synthesis File) correspond bien au nouveau module.

Description RTL

Les registres de données et de configuration/Protocole Avalon

Comme nous faisons un simple registre et que nous n’utilisons pas le signal waitrequest, les transactions Avalon devrait ressembler à ce qui est montré sur la figure suivante:

Ceci est autorisé par le protocole Avalon, mais, pour un système plus complexe, dans lequel cette augmentation du chemin combinatoire influe négativement sur la fréquence de fonctionnement, nous pouvons répondre en deux cycles en utilisant waitrequest. Ainsi, la sortie de notre module viendrait directement d’un registre.

NOTE Bien que nous ne l’utilisions pas, le signal read est émis par l’initiateur. Ici il faut comprendre que quand l’adresse change, nous devons renvoyer la donnée dans le même cycle (i.e. combinatoirement).

Prise en compte du registre enable

Tel que décrit dans la spécification, seules les pins pour lesquels nous avons écrit 1 dans le registre enable doivent être pris en compte/modifiés.

Environnement de simulation

Ça commence à se complexifier, pour identifier d’éventuels problèmes, nous vous proposons d’utiliser l’environnement de simulation disponible dans l’archive suivante.

Décompressez là dans le répertoire où se trouve votre fichier SystemVerilog.

Elle contient un module de test qui peut générer des transactions Avalon ainsi qu’un testbench (testbench.sv) avec quelques exemples de son utilisation.

Le fichier filelist content la liste des fichiers qui seront compilés.

Le makefile permet de:

Lancez donc la simulation et vérifiez que le comportement de votre module est correct (modifiez éventuellement le testbench pour tester progressivement votre module).

Mise en œuvre des interruptions

Le schéma suivant montre comment un signal d’interruption interne au module peut être généré, en fonction de l’activation, la polarité et le masque, à partir des entrées.

Ce signal doit permettre de générer l’interruption qui remonte vers le processeur. S’il passe à 1, l’information doit être conservée et n’est remise à zéro que si on écrit à l’adresse du registre irq ack.

Test sur la maquette

Pour tester sur la maquette, écrivez un programme de test en C permettant de vérifier que tout fonctionne.

Pour tester les interruptions, la fonction RegisterISR permet d’associer un handler à l’une des entrées d’interruption su processeur. La fonction irq_enable autorise globalement les interruptions.

Attention si vous voulez utiliser la fonction printf vous devrez augmenter la taille de la mémoire embarquée (64K par exemple). La fonction simple_printf nécessite moins de mémoire, mais, elle n’implémente pas tous les formats.

Bonus: Amélioration du contrôleur de GPIO

Si vous avez tout testé, vous pouvez ajouter les fonctionnalités suivantes: