Aller au contenu

Utilisation d'une mémoire tampon pour l'image

ATTENTION

TAG GIT pour cette ETAPE : SDRAM

Objectif

La carte DE10-Nano comporte 1GB de mémoire SDRAM (voir DDR3, Figure 2-1 de la documentation de la carte). Cette mémoire est partagée entre le HPS (les processeurs ARM) et la partie FPGA. Nous allons utiliser une zone de cette mémoire comme mémoire d'image, ou frame buffer, pour stocker la mire plutôt que de la générer à la volée.

  • Le module vga devra donc lire de façon régulière le contenu de cette zone mémoire, de manière à fournir les pixels pour l'écran.
  • La mire, elle-même, sera créée par le système Linux qui tourne sur le HPS.

Les "Synchronous Dynamic Ram" et les contrôleurs de DRAM.

Avant d'utiliser cette mémoire, il est bon de connaitre quelques particularités de ce type de composant afin de bien comprendre la nécessité d'utiliser un contrôleur de DRAM. L'accès aux données d'une SDRAM n'est pas aussi simple que l'accès aux données d'une mémoire statique. L'opération de transfert, que ce soit en lecture ou en écriture, nécessite une séquence d'opérations mettant en oeuvre plusieurs signaux de contrôle. D'autre part, cette séquence d'opérations dépend d'un état interne de la SDRAM. Enfin, à la différence de la mémoire statique, la SDRAM finit par perdre ses données même si son alimentation est maintenue. Il faut mettre en oeuvre, de manière régulière, des séquences dites de rafraichissement, pour maintenir les données dans la mémoire. En conséquence, nous retiendrons les points suivants :

  • L'usage d'une SDRAM nécessite l'utilisation d'une unité de contrôle spécialisée, prenant en charge le séquencement des opérations d'accès ainsi que le rafraichissement de la mémoire.
  • Le nombre de cycles nécessaires à l'accès à une donnée (que ce soit en lecture ou en écriture) n'est pas garanti, car il dépend de l'état interne de la SDRAM.
  • Enfin, la SDRAM étant partagée entre le HPS et le FPGA, le temps d'accès peut dépendre de la charge logicielle.

Nous vous fournissons pour cette étape, un accès au contrôleur de sdram compatible avec le bus Avalon. Vous n'aurez donc pas à vous soucier du détail de l'accès à la SDRAM, mais simplement à générer des requêtes Avalon correctes.

1/ Première adaptation du module VGA

  • Dans les entrées/sorties du module vga, ajoutez un port correspondant à l' interface avalon_if de type host et nommée avalon_ifh. Cette interface permettra d'échanger des données de 32 bits avec la SDRAM à une fréquence de bus de 100MHz.

Dans une première phase, modifions vga pour créer un hôte Avalon très simple dont le seul rôle est de vérifier une instanciation correcte des éléments: insérez le code suivant en fin du module vga.

Attention

Dans la suite, ce code sera supprimé et réécrit pour correspondre aux spécifications.

// Code de test de l'interconnection avalon_sdram (non synthétisable)
assign avalon_ifh.address = '0 ;             // Adresse fixe à 0
assign avalon_ifh.burstcount = 6'h1 ;        // Transfert d'une donnée 
assign avalon_ifh.writedata = 32'hBABECAFE ; // On écrit toujours la même chose
assign avalon_ifh.byteenable = 4'hF ;        // On écrit tous les octets
initial begin
    {avalon_ifh.write, avalon_ifh.read} = 2'b00 ;
    @(posedge avalon_ifh.reset) ;
    @(negedge avalon_ifh.reset) ;
    repeat(10) @(posedge avalon_ifh.clk) ;
    avalon_ifh.write <= 1'b1 ;
    @(posedge avalon_ifh.clk iff !avalon_ifh.waitrequest) ;
    avalon_ifh.write <= 1'b0 ;
    repeat(10) @(posedge avalon_ifh.clk) ;
    avalon_ifh.read <= 1'b1 ;
    @(posedge avalon_ifh.clk iff !avalon_ifh.waitrequest) ;
    avalon_ifh.read <= 1'b0 ;
    @(posedge avalon_ifh.readdatavalid) ;
    repeat(10) @(posedge avalon_ifh.clk) ;
    $stop() ;
end
// Fin du code de test

Ne cherchez pas à simuler pour l'instant, compilez éventuellement directement avec /comelec/softs/bin/vlog vga.sv pour vérifier la syntaxe.

2/ Adaptation du module Top

Dans le module Top, vous disposez déja d'une instance de l'interface avalon_if_sdram vers le contrôleur de sdram, le code est le suivant:

  avalon_if #( .DATA_BYTES(4)) avalon_if_sdram  (sys_clk, sys_rst);
  • Cette interface est pour le moment neutralisée. Il faut supprimer cette neutralisation. Pour cela, commentez la zone de code où l'on assigne des valeurs constantes aux signaux de avalon_if_sdram.

  • Enfin, modifiez l'instanciation de vga dans Top de manière à connecter le port avalon_ifh de vga à l'interface avalon_if_sdram.

3/ Première simulation

Le testbench Tb_Top.sv contient déjà tous les modules nécessaires pour simuler l'accès à la SDRAM. Il suffit donc de lancer la simulation:

  • Compilez (make compile) et simulez (make simu_batch).
  • Corrigez les différentes erreurs.
  • Relancez la simulation en mode graphique et visualisez les signaux internes à l'interface avalon_if_sdram dans Top.
  • Vérifiez le bon fonctionnement du bus avalon_if_sdram: Une transaction en écriture suivie d'une transaction en lecture. La donnée lue (readdata) doit être identique à la donnée écrite (writedata).

4/ Adaptation du module VGA pour la lecture de la SDRAM

Les caractéristiques du stockage de l'image en SDRAM sont les suivantes:

  • Les pixels de l'image que l'on désire afficher sont stockés dans la mémoire par mots de 32bits, la couleur étant codée sur les 3 octets de poids faible.
  • Les pixels sont stockés de manière consécutive en mémoire, à partir de l'adresse 0.
  • Seuls les pixels de la zone affichable de taille VDISPxHDISP sont stockés.
  • Les adresses Avalon sont des adresses "octet": les adresses consécutives des pixels sont 0,4,8...

Créez un contrôleur d'accès à la SDRAM dont les caractéristiques sont les suivantes:

  1. Le contrôleur utilise l'horloge clk et la remise à zéro reset du bus avalon_ifh.
  2. Le contrôleur génère exclusivement des requêtes en lecture pour balayer l'intégralité de l'image en mémoire.
  3. Le contrôleur reboucle indéfiniment sur la lecture de l'image.
  4. Le contrôleur utilise des rafales de lectures de longueur définie par un paramètre (localparam) nommé BURSTSIZE.
  5. La valeur de BURSTSIZE doit pour l'instant être fixée à 16 (16 pixels lus à chaque requête).
  6. Le contrôleur ne peut demander une nouvelle rafale que s'il a vérifié que BURSTSIZE données ont bien été reçues.
  7. Après réception d'une rafale, le contrôleur attend au moins un cycle sans requête avant de demander une nouvelle rafale.

Remarques:

  • Le nombre total de pixels de l'image est un multiple de 16 que ce soit en simulation ou pour l'image finale.
  • N'oubliez pas de supprimer le code de test de l'interface SDRAM
  • Vérifiez bien quels signaux doivent être pilotés par le contrôleur et quels signaux doivent être maintenus constants.

Après codage, simulez en l'état votre système:

  • Le testbench que nous fournissons contient une mire dont vous devriez voir les pixels défiler.
  • Vérifiez bien que vous lisez exactement HDISPxVDISP pixels et que vous enchaînez bien des relectures successives de l'image.
  • Vérifiez bien que vous respectez les 7 consignes données.

5/ Utilisation d'une FIFO pour adapter le flux des pixels à la sortie VGA

Les pixels lus dans la SDRAM arrivent à un rythme qui dépend des disponibilités du contrôleur de SDRAM et qui ne correspond pas à celui de la vidéo. De plus, l'horloge du bus Avalon (100Mhz) n'est pas corrélée avec celle de l'affichage VGA (32 Mhz).

Nous devons créer un tampon entre le processus de lecture de la SDRAM et le processus d'affichage des pixels. Pour cela nous utiliserons un bloc FIFO asynchrone nommé async_fifo.

Les paramètres et entrées/sorties de cette FIFO sont les suivantes:

Nom Type Utilisation
DATA_WIDTH paramètre Taille des données à écrire ou lire dans la FIFO.
DEPTH_WIDTH paramètre La fifo peut contenir au maximum 2**DEPTH_WIDTH données.
ALMOST_FULL_THRESHOLD paramètre Niveau de remplissage déclanchant le signal walmost_full
rst entrée remise à zéro de la FIFO
rclk entrée (LECTURE) : Horloge de lecture de la FIFO
read entrée (LECTURE) : Signal de demande de lecture dans la FIFO
rdata sortie (LECTURE) : Donnée lue dans la FIFO
rempty entrée (LECTURE) : Signal informant que la FIFO est vide.
wclk entrée (ECRITURE) : Horloge d'écriture de la FIFO
write entrée (ECRITURE) : Signal de demande d'écriture dans la FIFO
wdata entrée (ECRITURE) : Donnée écrite dans la FIFO
wfull sortie (ECRITURE) : La fifo est pleine.
walmost_full sortie (ECRITURE) : Le remplissage de la FIFO est supérieur à ALMOST_FULL_THRESHOLD

Dans une première étape nous ne nous neutraliserons la lecture dans la FIFO:

  1. Créez une instance de async_fifo dans VGA, en respectant les consignes suivantes:

    • Le port rst est connecté au reset de l'interface avalon_ifh
    • Le port rclk est connectée à l'horloge pixel_clk
    • Le port read est forcé à 1'b0
    • Le port rdata est laissé non connecté (.rdata(),...)
    • Le port rempty est laissé non connecté (.rempty(),...)
    • Le port wclk est connecté à l'horloge du bus Avalon.
    • Le port wfull est laissé non connecté (.wfull(),...)
    • Le port walmost_full est connecté à un signal walmost_full que vous créez pour l'occasion.
  2. Déterminez, vous-même, quel signal du bus Avalon peut être connecté au port wada

  3. Déterminez, vous-même, quel signal du bus Avalon peut être connecté au port write
  4. Le paramètre DATA_WIDTH sera de la largeur des pixels.
  5. Le paramètre DEPTH_WIDTH devra permettre de stocker 256 pixels dans le tampon.

ATTENTION

Le contrôleur d'accès SDRAM devra tenir compte de l'état de la FIFO avant d'envoyer une requête sur le bus Avalon. Nous utiliserons pour cela le signal walmost_full qui dépend du paramètre ALMOST_FULL_THRESHOLD.

  • Configurez le paramètre ALMOST_FULL_THRESHOLD pour être sûr, en toute circonstance de garantir que la fifo ne sera jamais pleine.
  • Adaptez le contrôleur Avalon pour qu'il tienne compte de l'état du signal walmost_full avat de lancer une requête.

Simulez votre système

vous devriez voir une série de lectures sur le bus Avalon suivies d'un arrêt dès que la FIFO est pleine.

6/ Lecture de la FIFO.

  • Supprimez ou commentez dans votre code la partie génération de MIRE.
  • Les pixels affichés proviendront directement de la FIFO: connectez la sortie rdata de la fifo au signal RGB.
  • Les pixels seront lus dans la FIFO uniquement dans la zone d'affichage. Votre code dispose déjà de signaux permettant de créer le signal read dans la FIFO.

ATTENTION

Lorsque l'on réinitialise la maquette, le contrôleur de SDRAM peut prendre beaucoup de temps avant d'être disponible. Dans la pratique cela se traduit par une attente relativement longue de la remise à zéro du signal avalon_ifh_waitrequest ce qui bloque toute écriture dans la FIFO. Si pendant ce temps, l'affichage VGA a démarré, l'image générée sera décalée et déformée...

  • Il faut mettre en place un mécanisme d'attente, coté VGA pour être sûr que la FIFO contient bien un minimum de données avant la lecture d'un premier pixel.

Pour cela, vous pouvez mettre en place un drapeau enable_read qui servira à valider le signal read envoyé à la FIFO. Ce signal peut être généré de la façon suivante:

  • Au reset enable_read=0
  • Si le signal almost_full_threshold est à 1 et que l'afficheur VGA est dans une zone de blanking vertical alors enable_read est mis à 1 et le reste indéfiniment.

Enfin n'oubliez pas qu'il n'est pas possible d'arrêter la lecture de la FIFO pendant la zone d'affichage.

  • Passez à la simulation

Si vous avez respecté ce cahier des charges vous devriez obtenir ce type d'image:

L'image en SDRAM

7/ Synthèse et placement routage

Ajoutez la ligne suivante dans le fichier syn/scripts/project_list.tcl, pour ajouter le module async_fifo dans liste des modules à synthétiser. Placer cette ligne avant la ligne du module vga.

global_assignment -name SYSTEMVERILOG_FILE $PROJECT_DIR/ips/fifos/async_fifo.sv
  • Lancez la synthèse.
  • Corrigez les éventuelles erreurs (si nécessaire, refaites la simulation après correction.)

8/ Test sur la maquette

ATTENTION

Comme indiqué en introduction, la mire affichée est générée par le HPS. Ainsi, pour que tout fonctionne bien, il faut démarrer le système Linux.

  • Programmez le FPGA (make program)
  • Redémarrez le système Linux. Pour cela il faut appuyer sur le bouton WARM_RST que vous pouvez trouver dans la vue suivante.
  • Il faut attendre 10 à 30 secondes et vous devriez voir une mire.
  • Vous pouvez utiliser une autre mire en changeant la position de l'interrupteur SW[0], le plus a droite des 4 interrupteurs entourés en vert.

Les boutons

Conclusion

Votre contrôleur VGA est maintenant capable d'afficher des images stockées dans la mémoire SDRAM. L'étape suivante consistera à mettre en place une architecture permettant de modifier le contenu de la mémoire d'image sans perturber la lecture mise en place

N'OUBLIEZ PAS

TAG GIT pour cette ETAPE : SDRAM