Étape 3: Un contrôleur de SDRAM

ATTENTION : TAG GIT pour cette ETAPE : "SDRAM"

0) Objectif

Le FPGA de la carte DE1-SoC est relié à une mémoire dynamique SDRAM de 64 Mega-octets. Nous allons utiliser cette mémoire comme mémoire d'image ou  "frame buffer" pour stocker l'image à  afficher, plutôt que de générer une mire "à la volée". Le module vga viendra donc lire de façon régulière le contenu de cette mémoire, de manière à fournir les pixels pour l'écran.

1) 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écéssité d'utuliser un "contrôleur de 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 fini 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 raffraichissement 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 raffraichissement 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.
  • La SDRAM nécessite un temps minimal à respecter après se mise en route pour être opérationnelle: après la mise en route du système, les premiers accès à cette mémoire devront attendre la disponibilité de la mémoire. 

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

2) Mise en place de l'environnement de travail

Placez vous dans votre dépot  git personnel (cd )
Importez les codes de testbench de votre futur bloc:  ​​

  • git checkout master
  • git remote update
  • git merge projet_video/étape3

Le répertoire fpga a été garni de nouveaux fichiers. Vous disposez d'un nouveau répertoire wb16_sdram16 contenant le contrôleur de SDRAM, et d'un répertoire fifo_async contenant le modèle d'une FIFO asynchrone qui sera utile plus loin.

Le module wb16_sdram16 est accompagné d'un module simulable d'une mémoire SDRAM. Cela permet de vérifier, par simulation, l'ensemble des opérations d'écriture et de lecture dans la SDRAM.

  • Placez vous dans le répertoire wb16_sdram16/simu, lancez une simulation par la commande make simu_batch et vérifiez que tout se passe bien. (Cette simulation instancie un testeur de requètes WishBone, le contrôleur et une SDRAM).

3) Intégration du modèle de la SDRAM dans votre testbench

Nous allons tout d'abord modifier le testbench  tb_fpga.sv pour tenir compte de l'existance de la SDRAM sur votre carte DE1-SoC. Nous avons défini, pour vous, une interface nommée sdram_if . Le modport maitre sera utilisé par le contrôleur de SDRAM, le modport esclave correspond à la SDRAM elle même.  Pour instancier l'interface :

  • ajoutez les lignes suivantes dans le code du module tb_fpga:
sdram_if sdram_if0();

Nous allons maintenant instancier le modèle de simulation de la SDRAM. Ce modèle étant tiré d'un modèle Verilgo classique, il ne contient pas d'ineterface mais des ports individuels séparés.

  • Ajoutez le code suivant dans votre module tb_fpga:
  sdr #(.Debug(0)) SDRAM
  (
                  .Clk    (sdram_if0.clk     ),
                  .Cke    (sdram_if0.cke     ),
                  .Cs_n   (sdram_if0.cs_n    ),
                  .Ras_n  (sdram_if0.ras_n   ),
                  .Cas_n  (sdram_if0.cas_n   ),
                  .We_n   (sdram_if0.we_n    ),
                  .Addr   (sdram_if0.sAddr   ),
                  .Ba     (sdram_if0.ba      ),
                  .Dq     (sdram_if0.sDQ     ),
                  .Dqm    (sdram_if0.dqm     )
 ) ;

Vous remarquerez que nous avons connecté chaque port de la SDRAM au signal correspondant dans l'interface. Enfin le modèle sdr dispose d'un paramêtre Debug dont voici l'usage:

  • Debug = 0 : le modèle de la SDRAM effectue les opérations de manière silencieuse.
  • Debug = 1 : le modèle de la SDRAM détaille toutes les opérations de la SDRAM (lecture, ecriture, choix de banc, raffraichissement)

Vous pouvez utiliser éventuellement l'option Debug=1 en cas de doute sur les lectures dans la SDRAM.

Enfin, le module fpga va devoir communiquer avec l'interface sdram_if0:

  • Modifiez, dans le module tb_fpga, les entrées sorties de l'instanciation de fpga en ajoutant la connection suivante:
.sdram_ifm(sdram_if0)

Remarquez que nous avons supposé que le port correspondant à l'interface SDRAM dans fpga sera nommé sdram_ifm

4) Adaptation du module FPGA, et intégration du contrôleur de SDRAM dans le FPGA

  • Dans le module fpga, ajoutez dans la liste des ports, la nouvelle interface sdram
           module fpga ....(
           ...
           // Partie sdram
           sdram_if.master sdram_ifm
           );

Le contrôleur de SDRAM, devra communiquer via un bus Wishbone  avec le bloc vga. Nous utiliserons une horloge à 100Mhz pour faire fonctionner le bus Wishbone. Il nous faut donc disposer d'une telle horloge (wshb_clk) ainsi que du signal de reset actif à l'état haut associé (wshb_rst). Enfin, la SDRAM nécessite une horloge sdram_clk, de même fréquence que wshb_clk, mais légèrement déphasée pour garantir le bon transfert des données entre le FPGA et la SDRAM. Nous avons préparé une PLL nommée wshb_pll, ayant les bonnes configurations pour cela. 

  • Instanciez wshb_pll dans le module fpga:
// Instanciation de la PLL pour générer la clock wishbone et la clock sdram
logic wshb_clk ;
logic wshb_rst ;
logic sdram_clk;
logic locked ;
wshb_pll pll(
  .refclk(fpga_CLK),
  .rst(!fpga_NRST),
  .outclk_0(wshb_clk),
  .outclk_1(sdram_clk),
  .locked(locked));

Il faut ensuite, générer le signal  wshb_rst. Pour cela:

  • Dans le module fpga, instanciez votre générateur de reset dans fpga. Il aura pour entrées, wshb_clk et  fpga_NRST.

Il faut créer l'interface Wishbone qui servira aux échanges de données entre le contrôleur de mémoire et votre afficheur VGA.

  • Dans le module fpga, instanciez une interface Wishbone dont le bus de donnée est de 16 bits (en effet la taille des données stockées dans la SDRAM est de 2 octets de large), et utilisant les signaux précédemment définis. :
// Instanciation d'un bush Wishbone 16 bits
wshb_if #(.DATA_BYTES(2)) wshb_if_0(wshb_clk,wshb_rst);
  • Instanciez maintenant le contrôleur de SDRAM fourni, connecté à la fois à l'interface wishbone et à l'interface SDRAM:
// Instanciation du controleur de sdram
wb16_sdram16 u_sdram_ctrl
  (
   // Wishbone 16 bits slave interface
   .wb_s(wshb_if_0.slave),
   // SDRAM master interface
   .sdram_m(sdram_ifm),
   // SDRAM clock
   .sdram_clk(sdram_clk)
  );
  • Enfin, modifiez l'instanciation du module vga dans le module fpga, de façon à le connecter à l'interface Wishbone wshb_if_0. Vous pouvez nommer le nom du ports correspondant wshb_ifm dans le module vga de manière à vous souvenir que le module vga est un maître du bus wishbone

5) Première adaptation du module VGA

  • Dans les entrées/sorties du module vga, ajoutez un port correspondant à  interface Wishbone 16bits de type master et nommée wshb_ifm.

Dans une première phase, nous allons modifier vga pour créer un maitre Wishbone "bidon" dont le seul rôle est de vérifier une instanciation correcte des différents éléments:

  • Générez sur wshb_ifm des requètes d'écriture permanentes, en assignant les valeurs constantes suivantes:
Nom valeur Commentaire
wshb_ifm.dat_ms 16'hBABE Donnée 16 bits émises
wshb_ifm.adr '0 Adresse d'écriture
wshb_ifm.cyc 1'b1 Le bus est sélectionné
wshb_ifm.sel 2'b11 Les deux octest sont à écrire
wshb_ifm.stb 1'b1 Nous demandons une transaction
wshb_ifm.we 1'b1 Transaction en écrtiture
wshb_ifm.cti '0

Transfert classique

wshb_ifm.bte '0 sans utilité.

6) Première simulation

  • Tentez de compiler (make compile) et de simuler (make simu_batch) la chaine totale tb_fpga
  • Corrigez les différentes erreurs.
  • Relancez la simulation en mode graphique  et visualisez les signaux du module fpga ainsi que les signaux internes à l'interface Wishbone  et à l'interface SDRAM
  • Vérifiez que le bloc vidéo fonctionne toujours
  • Vérifiez que 2 nouvelles horloges à 100 Mhz sont présentes
  • Vérifiez que sur le bus Wishbone, rien ne se passe pendant 2ms environ, puis que des transactions démarrent. En effet, la mémoire SDRAM nécessite un temps d'initialisation avant d'accepter les transactions.
  • Que des données "BABE" transitent vers la SDRAM

Si vous ne voyez pas tout cela, en désespoir de cause, faites appel à un encadrant....

7) Adaptation finale du module VGA pour la lecture de la SDRAM

Modifiez le module vga pour qu'il fonctionne de la façon suivante (lisez toute l'explication avant de commencer le codage)

7.1/ Lecture en SDRAM

  • Après le reset, la génération des signaux de synchronisation de vga reste inchangée.
  • Après le reset, un contrôleur (codé par vous même) génère des requètes de lectures de pixels sur l'interface Wishbone, en bouclant en permance dans la mémoire pour lire une mire supposée stockée dans la mémoire SDRAM
    • Les pixels sont stockés par mots de 16bits en format RGB565 (5bits pour le rouge, 6 bits pour le vert, 5 bits pour bleu)
    • Seuls les pixels de la zone affichable de taille VDISPxHDISP sont à stocker.
    • Les pixels sont stockés de manière consécutive dans la mémoire 
    • Attention les adresses Wishbone sont des adresses "octet" :un  pixel de coordonnée (x,y) sera stocké, par exemple, en adresse 2*(HDISP*y+x).
    • Les transactions sur le bus Wishbone sont validées par le signal stb. Le signal cyc étant maintenu à 1 en permanence. 
    • Seul le retour du ack en provenance de la mémoire fait avancer le processus à la lecture du pixel suivant.
    • Un fois l'image entièrement lue, le processus boucle sur lui même indéfiniment pour relire en permanence toute l'image..
  • Vous pouvez simuler en l'état votre système, le modèle de SDRAM 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 réenchaînez bien des relecture successives de l'image.

7.2/ Ecriture en FIFO

Les pixels lus arrivent à un rythme qui dépend des disponiblilité du contrôleur de SDRAM et correspond pas à celuil de la vidéo et de plus l'horloge du bus Wishbone n'est pas correllée avec cela de l'affichage VGA. Nous allons utiliser une FIFO asynchrone pour faire le tampon entre le processus d'écriture et l'affichage des pixels.

  • Nous utiliserons une fifo de 256 données de 16 bits. Son code vous est fourni. Attention, le paramètre DEPTH_WIDTH ne représente pas le nombre de données mais le log2 du nombre de données (fournir 8 pour obtenir 256...) 
    • Instanciez la FIFO dans le code de VGA en créant de nouveaux signaux dont vous affecterez les valeur plus tard...
  • Les pixels lus sur le bus Wishbone sont écrits dans la FIFO au rythme de leur arrivée et de l'horloge Wishbone.
  • Si la FIFO est pleine, le contrôleur de lecture doit arrêter de faire des requètes sur le bus et doit attendre que la FIFO ne soit plus pleine (il faut modifier votre contrôleur précédent pour cela).
  • Vous pouvez ici aussi simuler et vérifier l'arrêt des lectures Wishbone et écriture FIFO dès que la FIFO est pleine.

7.3/ Lecture de la  FIFO

Le contrôleur VGA devra piloter la lecture des PIXELS dans la FIFO, tout en garantissant que la première donnée lue corresponde au premier pixel affichable de l'écran. Pour cela le dispositif sera le suivant:

  • La lecture dans la FIFO se fait au rythme de l'horloge pixel.
  • La toute première lecture ne pourra pas avoir lieu tant que la FIFO n'aura pas été FULL au moins une fois.
  • Attention, le signal wfull de la FIFO est dans le domaine de l'horloge wshb_clk et vous vouler l'exploiter dans le domaine de l'horloge local_vga_clk. Vous savez ce qu'il faut faire....
  • Les lecture ne pourront avoir lieu que pendant la zone affichable de l'image (VGA_BLANK) qui correspond exactement à HDISPxVDISP pixels
  • Supprimez le générateur de mire: Les pixels lus dans la fifo seront étendus sur les 3x8 bits de R, G et B par extraction et en recadrage des différentes portions du mot de 16 bit lu

8) Simulation et vérification du résultat.

Si vous avez respecté ce cahier des charges, et utilisé les mếmes résolutions pour la simulation que celles proposées dans les étapes précédentes, vous devriez obtenir ceci:

9) Synthèse et test sur la maquette DE1-Soc

  • Ajoutez les nouveaux modules en entête du fichier file_list.tcl
# Ajout du bus wishbone et de la sdram
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/fpga/src/wshb_if.sv
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/fpga/src/wshb_pll.sv
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/wb16_sdram16/src/sdram_if.sv
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/wb16_sdram16/src/xess_sdramcntl.sv
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/wb16_sdram16/src/wb_bridge_xess.sv
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/wb16_sdram16/src/wb16_sdram16.sv
set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/fifo_async/src/fifo_async.sv
  • Ajoutez l'interface SDRAM dans la listes des pattes d'entrées sorties du FPGA (pins_assignment.tcl) . Ajoutez la ligne suivante:
# La sdram
source pins_assignment_sdram.tcl
  • Nettoyez le répertoire (make clean)
  • Relancez la synthèse
  • Corrigez les éventuelles erreurs (si nécessaire refaites la simulation après correction)

Parmi les messages d'information et de Warning, un concerne les analyses de timing donnant des résultats faux.En effet, nous avons introduit deux nouvelles horloges.

  • Repérez dans le fichier de log, le nom de l'horloge correspondant à l'horloge à 100Mhz du bus Wishbone, et introduisez la dans la liste des horloges indépendantes (set_clock_group...) comme vous l'avez déjà fait pour l'horloge VGA. (fichier timing_constraints.sdc)
  • Resynthétisez votre circuit

Avant de tester sur la maquette:

Nous avons mis en place un dispositif de lecture d'image, mais nous n'avons rien stocké dans la SDRAM. Un test direct sur la maquette ne peut rien donner de bon. Nous avons préparé pour vous un "design" permettant de précharge la SDRAM. Pour tester votre design, utilisez les ommandes suivantes :

  • make load_sdram
  • make program

Attention, si vous éteignez la maquette, le contenu de la SDRAM n'est pas garanti. De même, si vous chargez dans la maquette un design ne comportant pas de contrôleur SDRAM et que vous attendez longtemps  (qq minutes ?) il se peut que le contenu de la SDRAM disparaisse.

N'OUBLIEZ PAS  : TAG GIT pour cette ETAPE : "SDRAM"

Fichier attachéTaille
Image icon screen_dump.png377 octets