Affichage Vidéo

Introduction

L'objectif de ce microprojet est de bâtir une architecture de "carte graphique", pour affichage de vidéo sur la maquette FPGA DE1-SoC. Le travail consistera, partant d'une "page blanche"  à construire pas à pas un environnement complet de simulation et de synthèse permettant de vérifier chaque étape du travail:

  • Les contraintes liées à l'implantation sur du matériel "Réel" seront traitées tout au long du projet.
  • Les étudiants disposeront de briques de base (contrôleur de mémoire, générateur vidéo (logiciel),...)
  • Les étudiants disposeront des documentations techniques (carte DE1-SoC, mémoire SDRAM, paramètres VIDEO,...)
  • Les échanges entre blocs internes seront basées sur le protocole WishBone

Le système sera composé des éléments suivants :

  • Un contrôleur  VIDEO  permettant d'afficher une image sur un écran VGA.
  • Une SDRAM servant de mémoire d'image, lue en permanence par le contrôleur video.
  • Le HPS (pour Hard Processor System), un double processeur ARM Cortex A9 intégré dans le FPGA
  • Un décodeur vidéo logiciel, fonctionnant sur le HPS et écrivant les images dans la SDRAM à travers le FPGA.
  • Un arbitre permettant de partager l'accès à la SDRAM entre le contrôleur vidéo et  le décodeur vidéo. 

Étape 1: Un squelette de projet

ATTENTION : TAG GIT pour cette ETAPE : "SQUELETTE" 

0) Objectif

Nous allons mettre en place un squelette de projet, utilisant quelques ressources disponibles sur la carte fpga DE1-SoC. Nous utiliserons quelques interrupteurs, boutons poussoirs, leds et horloges qui servirons tout au long du projet. Plutôt que de vous fournir un squelette "prêt à servir" nous vous laisserons bâtir vous-même ce squelette de façon à vous familiariser avec l'ensemble des données et informations à fournir pour mettre en œuvre un design réel sur la carte.

1) 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 add projet_video git@gitlab.enst.fr:se204/projet_video.git
  • git remote update
  • git merge --allow-unrelated-histories projet_video/étape1

2) Arborescence de travail 

Nous partons de rien, ou presque... Nous utiliserons une arborescence de répertoires telle qu'indiquée ci-dessous. Pour chaque  bloc matériel créé, nous disposerons d'un répertoire spécifique destiné à contenir les fichiers source, de simulation ou de synthèse concernés.

Veuillez respecter les noms proposés pour faciliter un déboguage ultérieur, et la création de scripts automatiques...

-- projet_video
   -- fgpa
​      -- src
      -- tb_src
      -- simu
      -- synth
   -- bloc1
      -- src
      -- tb_src
      -- simu
   -- bloci ...

 

  • Pour cette étape nous ne travaillerons que sur le bloc fpga, au fur et à mesure que vous ajouterez d'autres  blocs.
  • Les sous-répertoires supplémentaires sont:
    • Le répertoire src qui ne devra contenir que du code synthétisables de vos blocs.
    • Le répertoire tb_src qui peut contenir tout code pouvant servir à la simulation (testbenchs...)
    • Le répertoire simu, répertoire dans lequel se trouvent les scripts de simulation et dans lequel se déroule la simulation.
    • Le répertoire synth,  répertoire dans le quel se trouvent les scripts de synthèse et dans lequel se déroule la synthèse. Il n'est pas vraiment indispensable pour les sous blocs
  • Le nom des répertoires devra être identique au nom  des modules concernés.

3) Le module fpga

Dans le répertoire "fpga/src" créez un premier fichier SystemVerilog  fpga.sv destiné à contenir le code global du fpga. Créez dans ce fichier un module nommé fpga et dont les entrées et sorties sont les suivantes:

 

Nom Type Nombre de bits Utilisation
fpga_CLK entrée 1 Horloge (à 50 Mhz)
fpga_CLK_AUX entrée 1 Horloge auxiliaire (à 27 Mhz)
fpga_LEDR0 sortie 1 Affichage LED
fpga_LEDR1 sortie 1 Affichage LED
fpga_LEDR2 sortie 1 Affichage LED
fpga_LEDR3 sortie 1 Affichage LED
fpga_SW0 entrée 1 commande 0/1
fpga_SW1 entrée 1 commande 0/1
fpga_NRST entrée 1 commande 0/1
fpga_SEL_CLK_AUX sortie 1 pilotage de l'horloge auxiliaire (l'horloge auxiliaire n'est disponible que si ce signal est forcé à 1)

Les entrées/sorties indiquées correspondront à de réelles entrées/sorties du FPGA. La carte DE1-SoC fournit  au fpga une horloge à 50 MHz ainsi qu'une horloge pilotable à 27Mhz

 Écrivez un code dans le module permettant :

  • de recopier la valeur  du signal fpga_SW0 vers fpga_LEDR0. 
  • de recopier la valeur du signal fpga_SW1  vers fpga_SEL_CLK_AUX 
  • de  faire clignoter le signal fpga_ LEDR1 à  approximativement 1Hz en utilisant l'horloge à 27 Mhz (utilisez un simple compteur binaire dont le reset, actif à l'état bas,  provient du signal fpga_NRST)
  • de faire clignoter le signal  fpga_LEDR2 à  approximativement 1Hz en utilisant l'horloge à 50 Mhz (utilisez un simple compteur binaire dont le reset, actif à l'état bas, provient du signal fpga_NRST)
  • de recopier l'état de fpga_NRST vers fpga_LEDR3.

4) Premier Environnement de simulation

Il s'agit de créer un environnement minimaliste reproduisant les conditions de fonctionnement réelles du FPGA.

Dans le répertoire fgpa/tb_src, créez un fichier SystemVerilog tb_fgpa.sv destiné à contenir le testbench. Créez dans ce fichier un module tb_fpga contenant 

  • Un processus générateur d'horloge à 50Mhz
  • Un processus générateur d'horloge à 27Mhz piloté par fpga_SEL_CLK_AUX ( l'horloge ne fonctionne que si fpga_SEL_CLK_AUX est égal à 1).
  • Un processus générant quelques actions sur le signal fpga_SW0 (test de l'allumage et l'extinction de la led)
  • Un processus générant quelques actions sur fpga_SW1 (vérification du pilotage de l'horloge auxiliaire)
  • Un processus générant quelques actions sur le signal fpga_NRST
  • l'instanciation du module fpga.

Pour les générateurs d'horloge, vous pouvez vous inspirer du testbench du filtre médian "median-tb.sv"  dans le répertoire simulation de votre projet median. Vous remarquerez que l'on fait simplement une boucle qui fait avancer le temps d'une demi période d'horloge entre chaque changement du signal d'horloge.  La valeur des unités de temps peut être indiquée, et cela peut être un nombre réel.  Attention la précision de ce nombre dépend de la précision avec laquelle le simulateur calcule le temps. Nous avons réglé pour vous cette précision à 1ps dans le Makefile de simulation.

Enfin, le générateur d'horloge à 27Mhz est un peu plus compliqué car il doit être commandable: faite attention à la manière dont vous écrivez son code car il est très facile de réaliser un processus avec boucle infinie ou le temps n'avance pas....

5) Première simulation

Dans le répertoire fpga/simu, vous disposez un fichier Makefile initial que vous adapterez au fur et à mesure des différentes étapes.  

Avant de lancer la simulation, prenez le temps d'une petite réflexion: Pour compter une seconde avec une horloge à 50Mhz, il faut simuler quelques millions de cycles de l'horloge, vous risquez d'attendre longtemps devant votre écran avant de voir bouger les signaux fpga_LEDR1 ou fpga_LEDR2.

Vous pouvez obtenir un comportement différent en simulation et en synthèse en paramétrant vos compteurs de façon conditionnelle : 

  • Nous avons défini, dans le Makefile, au moment de la compilation des sources, une variable du préprocesseur nommée "SIMULATION". 
  • Vous pouvez utiliser les commandes du préprocesseur `ifdef, `else, `endif  pour définir des paramètres de comptage différents en fonction de la situation

Voici un exemple  (les valeurs proposées ne sont ici totalement arbitraires)

`ifdef SIMULATION
  localparam hcmpt=5 ;
`else
  localparam hcmpt=18 ;
`endif

Vérifiez "visuellement" (chronogrammes) le bon fonctionnement de votre "fpga" et de son environnement associé.

6) Premier environnement de synthèse 

La synthèse en vue d'un téléchargement sur la maquette DE1-SoC nécessite de connaître les ressources disponibles et leurs connexions au FPGA. D'autre part, les entrées/sorties d'un FPGA peuvent être programmées pour s'ajuster aux contraintes éléctriques et temporelles des composants extérieurs. Les outils de synthèse permettent de préciser cela à l'aide de fichiers de contrainte.

Vous disposez des fichiers suivants, prenez le temps de les examiner, car vous devrez adapter certains d'entre eux

Makefile:

Ce fichier permet de :

  • lancer la synthèse   : make syn
  • programmation du FPGA : make  program
  • nettoyer le  répertoire de synthèse : make clean

Ce fichier n'est normalement pas à éditer.  Deux variables d'environnement sont définies pour une utilisation dans les scripts de synthèse.

  • PROJET : c'est le nom du module principal (ici  fpga)
  • TOPDIR : le répertoire  principal du projet.

syn_DE1-SoC_quartus.tcl:

Ce fichier est le script de synthèse principal. Il est écrit en langage Tcl qui est un langage très utilisé dans le monde de la CAO électronique. Ce script met à jour le projet et fait appel aux différents scripts de configuration avant de lancer la synthèse. 

device_assignment.tcl:

Ce script permet de sélectionner le FPGA cible pour la synthèse. Il s'agit dans notre cas de celui de la carte DE1-SoC. Il faut être précis dans la désignation du circuit pour être d'une part sûr de son brochage et de l'estimation de performances faite par l'outil de synthèse. Si le script ne contient pas les lignes suivantes, alors vous risquez de ne pouvoir programmer votre FPGA...

set_global_assignment -name FAMILY "Cyclone V"
set_global_assignment -name DEVICE 5CSEMA5F31C6

pins_assignment.tcl:  

Ce  script permet de définir les entrées/sorties du FPGA.  Prenez le temps de lire les nombreux commentaires de ce fichier qui expliquent  les différents choix faits pour adapter convenablement les signaux entrant et sortant du FPGA aux caractéristiques des circuits externes.

A VOUS DE JOUER

  • Vous trouverez  dans le répertoire "documentations" la documentation technique de la carte DE1-SoC.
  • Pour chacune des entrées/sorties de votre module (horloge à 27 MHz,  horloge à 50 Mhz, bouton poussoir, interrupteur, leds)  déterminez un élément extérieur au FPGA susceptible d'être utilisé en vous aidant du tableau ci-dessous
  • Nom de la pin Ressource pouvant être utilisée
    fpga_CLK voir chapitre 3.5 Clock Circuitry
    fpga_CLK_AUX voir chapitre 3.6.7 TV Decoder TD_CLK27
    fpga_SEL_CLK_AUX voir chapitre 3.6.7 TD_RESET_N
    fpga_LEDR* voir chapitre 3.6.1  "LEDs"
    fpga_SW* voir chapitre 3.6.1 "Slide Switches"
    fpga_NRST voit chapitre 3.6.1 "Push-buttons"
  • Pour chacun de ces éléments, déterminez la patte du fpga qui est connectée.
  • Complétez le fichier "pins_assignments.tcl"  sans oublier de définir le standard "électrique" de la connexion voulue

La syntaxe générale à utiliser est :

set_location_assignment PIN_xxx -to yyy

Ou xxx est le numéro de la patte choisie, et yyy est le nom du signal dans votre module fpga.

file_list.tcl:

Ce script permet de définir les fichiers HDL à charger (une ligne par fichier). 

La syntaxe générale à utiliser est :

set_global_assignment -name langage_source chemin_vers_le_fichier

Où  langage_source est une des trois valeurs SYSTEMVERILOG_FILE, VERILOG_FILE, VHDL_FILE suivant le langage utilisé et chemin_vers_le_fichier est le nom du fichier HDL à utiliser. Le chemin peut être absolu, ou relatif. Vous pouvez utiliser l'expression ${TOPDIR} pour vous référer à la racine du projet.

A VOUS DE JOUER:

Ajoutez le source de votre fpga dans la liste des fichiers

project_assignment.tcl:

Ce script permet de définir le comportement général souhaité par le synthétiseur. Dans notre cas nous demandons à l'outil Quartus d'optimiser les tentatives de synthèses successives en ne recompilant pas les fichiers déjà compilés sans erreur. C'est la raison pour laquelle, si vous recompilez alors qu'aucun fichier source n'a changé, Quartus ne fait rien...

timing_constraints.sdc:

 Ce script utilise une syntaxe devenue quasiment une norme de fait en CAO électronique pour définir les contraintes temporelles d'un design. Le suffixe "sdc" signifie "Synopsys Design Constraints" du nom de la société de CAD qui a créé cette syntaxe. Le concepteur doit fournir dans ce fichier toutes les informations "temporelles" susceptibles d'être une contrainte pour le design.

Pour le moment, dans notre cas la première contrainte est d'informer le synthétiseur de l'existence d'un domaine d'horloge nommé fpga_CLK ayant une fréquence de 50Mhz et associé au port fpga_CLK de notre design.  Nous faisons de même pour l'horloge auxiliaire.

create_clock -name {fpga_CLK} -period 20.0 -waveform { 0.0 10.0 } [get_ports {CLK}]
create_clock -name {fpga_CLK_AUX} -period 37.037  [get_ports {fpga_CLK_AUX}]

Enfin, nous informons l'outil qu'il n'y a pas de correlation entre ces deux horloges (il ne faudra pas tenter d'évaluer des temps de propagation entre des registres du premier domaine d'horloge et des registres du deuxième domaine d'horloge).

set_clock_groups -exclusive \
         -group {fpga_CLK} \
         -group {fpga_CLK_AUX}

Enfin, l'outil de synthèse ne pouvant pas deviner ce qu'est une LED, un switch ou un bouton poussoir, nous l'informons qu'il n'y a aucune contrainte de timing sur ces objets. Ces commandes définissent tout chemin temporel entre le switch ou les leds et tout noeud du design est à ignorer.

set_false_path -from [get_ports fpga_SW*] -to *
set_false_path -from [get_ports fpga_NRST*] -to *
set_false_path -from * -to [get_ports {fpga_LEDR*}]

7/ Première synthèse

Après avoir complété vos fichiers, lancez la synthèse (make).  Les messages apparaissant à la console sont colorés en vert pour les informations, en bleu pour les warnings, et en rouge pour les erreurs.  

Corrigez les éventuelles erreurs, et vérifiez les messages de Warning. Il ne devrait rester qu'un seul message de Warning :

Warning (15714): Some pins have incomplete I/O assignments. Refer to the I/O Assignment Warnings report for details

Si nous examinons le fichier de rapport fpga.fit.rpt, nous trouvons une ligne contenant "I/O Assignment Warnings" suivie d'une liste de Pin pour lesquelles le message est "Missing slew rate".

Nous avons en effet oublié de préciser à l'outil quel est le temps  de transition désiré sur les pattes d'entrée sortie du FPGA. Il est possible de régler grossièrement ce temps  de transition par un code entier valant 0 (Lent) ou 1 Rapide) . Le constructeur du FPGA donne cette possibilité pour laisser au concepteur le  choix d'un compromis entre la vitesse d'une entrée sortie et le bruit qu'elle génère dans le circuit.

A VOUS DE JOUER:

Modifiez le fichier pins_assignment.tcl de manière à définir pour chaque sortie le temps de transition des sorties suivant la syntaxe suivante: 

set_instance_assignment -name SLEW_RATE 1 -to le_nom_de_la_sortie

Vous pouvez vous inspirer des autres contraintes (IO_STANDARD, CURRENT_STRENGTH_NEW) pour affecter cette contrainte à toutes pins nommées "fpga_quelquechose"

Resynthétisez votre design et vérifiez que tous les Warnings ont disparu. 

8) Test sur maquette

La maquette DE2 étant connectée au PC par le câble USB (port USB-Blaster), tapez la commande make program pour charger le fpga. Vérifiez le fonctionnement du design, et entre autres

  • Que la LED 0 reproduit l'état du switch 0
  • Que la LED 1 (témoin du 27 Mhz) clignote si le switch 1 est à 1
  • Que la LED 2 (témoin du 50 Mhz) clignote
  • Que la LED 3 est allumée, et s'éteind si on appuie sur le bouton poussoir choisi pour fpga_NRST.

9) Le problème du RESET et de la métastabilité.

Nous avons utilisé un bouton poussoir comme signal de remise à zéro. Ce signal provient d'un monde qui n'est pas du tout synchrone avec l'horloge. Au moment du passage en reset, il n'y a pas de problème, que le signal soit utilisé comme reset synchrone ou asynchrone : le système finit toujours par être réinitialisé. Par contre, lorsque le bouton est relâché, il peut y avoir des phénomènes de métastabilité : dans un système complexe, certaines bascules changent d'état avant d'autres ce qui peut être très dangereux pour des machines à état (passage dans un état non prévu...)

Le schéma suivant se propose de résoudre ce problème d'une façon similaire à celle étudiée pour les changements de domaine d'horloge. Le signal fpga_NRST entrant doit servir de reset actif à l'état bas.  Il est connecté à l'entrée de reset (active à l'état bas ) des deux bascules D. Lorsque fpga_NRST vaut "0" les deux bascules D sont forcées à "0", donc n_rst=0. Lorsque NRST passe à "1", le signal constant "1" est échantillonné sur deux cycles et n_rst finit par passer à "1" de manière synchrone avec l'horloge. Le démarrage des systèmes seront bien synchrones avec leur horloge.

 

Resynchronisation du reset

A VOUS DE JOUER:

Réalisez un module (dans un fichier à part, stocké dans le répertoire fpga) de resynchronisation du reset. Nous aurons, plus tard, besoin de plusieur de ces modules. Ce module devra pouvoir être paramétrable. Les contraintes sont les suivantes:

  1. Une horloge entrante
  2. Un reset rentrant, actif à l'état bas.
  3. Un reset sortant actif, soit à l'état bas, soit à l'état haut selon la valeur d'un paramêtre fourni au module.

Modifiez votre design pour inclure le module de rééchantillonnage du reset proposé. (vous pouvez l'utiliser par exemple pour générer le reset du compteur sur l'horloge à 50Mhz).

Vérifiez la resynchronisation en simulation, faite la synthèse, puis rechargez le design.

10) CONCLUSION

 Vous disposez d'un squelette permettant de développer votre projet: 

  1. Conservez tout au long du projet les LEDs clignotantes qui permettent de savoir que votre design est actif. 
  2. Conservez de même le dispositif permettant de générer un reset "propre". 
  3. Les messages du synthétiseur sont nombreux. Dans les prochains travaux, vous devrez évidemment éliminer les Erreurs, mais aussi examiner soigneusement les Warnings et déterminer s'ils correspondent à un problème potentiel ou non. Dans la mesure du possible, tentez d'éliminer ces Warnings de façon à détecter plus facilement de nouveau problèmes au fur et a mesure que vous faites évoluer le code.
  4. Vous pouvez à tout moment utiliser la version graphique de l'outil Quartus pour examiner les résultats de synthèse. Pour cela il vous suffit d'ouvrir le projet créé dans l'outil. Faites cependant attention, si vous modifiez votre projet dans l'outil, vous ne retrouverez pas ces modifications dans vos scripts, et ainsi vous ne pourrez plus utiliser le Makefile pour recompiler votre projet.

N'OUBLIEZ PAS !!!  : TAG GIT pour cette ETAPE : "SQUELETTE" 

Étape 2: Un contrôleur vidéo VGA

ATTENTION : TAG GIT pour cette ETAPE : "VGA"

0) Objectif

Il s'agit de construire le code d'un contrôleur d'écran vidéo, c'est-à-dire un dispositif générant des signaux vidéo et les signaux de synchronisation associés permettant de contrôler un écran LCD.. Enfin, nous voulons plus qu'un simple "code qui marche", mais bel et bien un code paramétrable et réutilisable.

 

 

1) La norme VGA

La norme d'affichage VGA a été conçue pour afficher des flux vidéo sur un moniteur:

  • Les images sont transmises à rythme régulier défini par un signal de synchronisation spécifique (VGA_VS) (VS pour Vertical Synchro)
  • Chaque image est transmise de haut en bas ligne par ligne à un rythme régulier défini par un signal de synchronisation (VGA_HS). (HS pour Horizontal Syncho)
  • Chaque ligne est transmise de gauche à droite  point (ou pixel) par pixel à un rythme régulier défini par une horloge (VGA_CLK). (Horloge pixel)
  • Chaque pixel est transmis par ses 3 composantes colorées Rouge, Vert et Bleu (VGA_R, VGA_G, VGA_B) codées sur  8 bits. 
  • Le flux de pixels n'est pas constant à cause des intervalles dits de "blanking" nécessaires à la synchronisation de l'image et des lignes. Cette synchronisation ligne après ligne, image après image est un héritage des contraintes des anciens tubes des moniteurs à écran cathodiques. Ces espaces de "blanking" sont signalés par un signal spécifique VGA_BLANK actif à l'état bas dans notre cas.

La durée des lignes, des images et des intervalles de blanking sont normalisées (en nombre de cycles de VGA_CLK) et doivent être respectées pour garantir la bonne synchronisation de l'écran.

L'évolution temporelle des signaux peut être représentée sur un diagramme à deux dimensions (la dimension horizontale correspondant aux pixels, la dimension horizontale correspondant aux lignes.

 

 

Vous trouverez dans la documentation de la carte DE1-SoC, chapitre 3.6.6 en page 32, une description de la partie interface vidéo.

Le FPGA est connecté à un circuit de conversion et de resynchronisation ADV7123.

Les paramètres temporels choisis pour notre affichage VGA sont les suivant :

Paramètre Commentaire Valeur Unité
Fpix fréquence pixel 25,17 Mhz
Fdisp fréquence image 60 images/sec
HDISP Largeur de l'image affichée 640 pixels
VDISP Hauteur de l'image affichée 480 lignes
HFP Horizontal Front Porch 16 pixels
HPULSE Largeur de la synchro ligne 96 pixels
HBP Horizontal Back Porch 48 pixels
VFP Vertical Front Porch 11 lignes
VPULSE Largeur de la sync image 2 lignes
VBP Vertical Back Porch 31 lignes

Vous pouvez aussi trouver ici un calculateur de timings.

2) Mise en place de l'environnement de travail

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

  1. git checkout master
  2. git remote update
  3. git merge projet_video/étape2

Vous disposez maintenant d'un sous-répertoire vga dans le répertoire projet_video. Ce répertoire est déjà garni d'un certain nombre de fichiers qui faciliteront le codage, la simulation et la synthèse.

3/ Squelette du contrôleur VGA: l'interface VGA, l'horloge VGA, la gestion du reset.

  • Dans le répertoire vga/src, créez un fichier vga.sv qui contiendra un module nommé vga.  

Pour simplifier la définition des entrées/sorties, nous avons préparé la définition d'une "interface" vga_if dans le fichier  vga_if.sv

  • Créez le squelette du module (module vga... endmodule) en lui attribuant les entrées sorties suivantes :
Nom de l'entrée/sortie Type de l'entrée sortie Commentaire
clk Entrée horloge entrente du module
nrst Entrée reset actif à l'état bas
vga_ifm Modport maître de l'interface vga_if  Tous les signaux destinés à l'écran passeront par cette interface. Le module vga est un maître.

Comme vous l'avez sans doute remarqué en lisant les documentations sur les signaux VGA, la fréquence de l'horloge pixel dépend du format d'image choisi. Il est possible de disposer d'horloges programmables dans le FPGA par l'intermédiaire de blocs PLL (Phase Locked Loop) . Le code nécessaire pour utiliser une telle PLL et un peu "magico-magique" dans la mesure ou le langage SystemVerilog ne permet pas d'exprimer ce genre de fonctionnalité (pas d'inférence). Nous avons préparé un tel bloc pour vous (vga_pll.sv). Si vous prenez la peine de lire son contenu, vous comprendrez que ce bloc est capable de générer une horloge à 25.17 Mhz en partant d'une horloge à 27.0 Mhz.

  • Instanciez un bloc vga_pll dans le module vga en connectant les entrées sorties de la façon suivante :
Entrée/sortie Signal connecté dans vga commentaire
refclk clk L'horloge entrante dans vga servira de référence pour la PLL
rst !nrst La PLL a un reset actif à l'état haut que l'on dérive du reset actif à l'état bas entrant
outclk_0 vga_clk (nouveau signal interne à créer dans vga) Le signal sortant de la PLL est connecté à un signal interne au module vga.
locked locked (nouveau signal interne à créer dans vga) La PLL signale par "locked" que l'horloge sortante est présente et stable. Ce signal peut être utilisé pour interdire tout traitement tant que la PLL n'est pas stabilisée.

Enfin, nous voulons un reset "propre" pour nos traitements: 

  • Instanciez un bloc de resynchronisation du reset (vous avez déjà réalisé ce bloc) dans vga. Ce bloc devra générer un reset actif à l'état haut.Vous connecterez les entrées/sortie de la façon suivante:
Entrée/sortie Signal connecté dans vga commentaire
CLK vga_clk On resynchronise le reset sur vga_clk
RST_IN nrst On resynchronise le reset entrant
RST_OUT rst (nouveau signal à créer dans vga) C'est le reset actif à l'état haut que vous utiliserez pour tous les processus synchrones de vga_clk.

4/ Génération des signaux de synchro (dans le module vga)

Pour faire simple, il s'agit de générer un flux de pixels image après image, ligne après ligne, et de signaux de synchronisation associés.

  • Le cœur du contrôleur est donc constitué de deux compteurs imbriqués: un compteur de pixels dans une ligne, et un compteur de lignes dans une image. 
  • Tous les signaux sont synchrones de  l'horloge qui sera appelée vga_clk. Le signal de reset sera rst
  • Les constantes pour les timings seront fournies via les paramètres locaux (localparam) dont les noms sont indiqués en paragraphe 1.
  • Les paramètres HDISP et VDISP devront pouvoir être modifiés lors de l'instantiation du module vga.
  • La valeurs des paramètres par défaut seront celles correspondant à la norme VGA 60Hz (image 640x480).
  • Le dimensionnement des différents compteurs (nombre de bits) et les différentes comparaisons de valeur devront se faire par rapport aux paramètres prédéfinis ou des formules employant ces paramètres à l'exclusion de tout codage en "dur" de constantes. 
  • Le signal vga_ifm.VGA_SYNC sert à configurer le circuit de conversion externe, ici, il sera une constante forcée à 0.
  • Les  signaux de synchronisation  (vga_ifm.VGA_HS, vga_ifm.VGA_VS, vga_ifm.VGA_BLANK)  seront calculés à partir de l'état des compteurs pixels et ligne. Ils devront eux même être synchrones.
  • Enfin, le signal vga_ifm.VGA_CLK sera assigné comme l'opposé de vga_clk.  En effet:
    • Ce signal se propage sur la carte au même titre que les signaux de synchro.
    • Le circuit de conversion va utiliser l'horloge reçue pour rééchantillonner les signaux de synchro avec l'horloge reçue.
    • Rien ne garantit que l'horloge arrive avant ou après chaque signal de synchro, nous aurons donc une incertitude d'un cycle sur le changement des signaux de synchro
    • En utilisant une horloge en opposition de phase, nous nous donnons une marge de manœuvre de 1/2 cycle pour échantillonner tous les signaux de synchro sur le même front.
  • Ne cherchez pas à simuler pour l'instant, compilez éventuellement pour vérifier la syntaxe)

5/ Génération d'une mire (dans le module vga)

Il s'agit de générer une image "calculée" pour vérifier le bon fonctionnement de l'ensemble du module. La mire demandée sera très simple, par exemple un fond noir, des colonnes blanches tous les 16 pixels, des lignes blanches toutes les 16 lignes. Attention, les signaux de couleur sont codés sur 8 bits (min=0, max=255)

  • Générez les signaux vga_ifm.VGA_R, vga_ifm.VGA_G et vga_ifm.VGA_B  de manière synchrone de façon à générer la mire en vous appuyant sur les valeurs des compteurs de la génération de synchronisation.
  • Vous n'avez pas à vous soucier des zones de blanking pour la génération de la mire....
  • Ne cherchez pas à simuler pour l'instant, compilez éventuellement pour vérifier la syntaxe)

6/ Mise à jour du module fpga pour intégrer le module vga.

Le contrôleur vga fait partie de votre fpga. Vous l'intégrerez de la façon suivante:

  1. Créez une instance de vga dans fpga.
  2. Connectez le signal fpga_CLK_AUX a l'entrée clk de votre instance (l'horloge à 27 Mhz)
  3. Connectez le signal fpga_NRST à l'entrée nrst de votre instance (le reset général)
  4. Enfin connectez le port d'interface vga_ifm à une interface de même nom.

Cette dernière interface doit être directement connectée au monde extérieur :

  1. Modifier la liste des E/S du fpga en ajoutant un port nommé vga_ifm de type modport maitre d'une interface de type vga_if (syntaxe identique à celle utilisée dans vga)
  2. Enfin rendez le module fpga paramétrable:
    1. Ajoutez deux paramètres HDISP et VDISP ayant pour valeur par défaut 640 et 480.
    2. Modifiez l'instanciation de vga de façon à ce que les paramètres HDISP et VDISP ainsi créés se propagent à vga.
  3. Ne cherchez pas à simuler pour l'instant, compilez éventuellement pour vérifier la syntaxe)

7/ Mise à jour du testbench de fpga pour évaluer votre générateur VGA.

La véritable interface vga_if à laquelle est connecté le contrôleur vga via le module fpga doit être instanciée dans le tesbench:

  • Dans tb_fpga.sv, ajoutez une instance de vga_if. Vous pouvez lui donner un nom arbitraire, par exemple:
vga_if vga_if0() ;

 

  • Modifiez l'instanciation de fpga de façon à connecter le port vga_ifm à l'interface créée précédemment.
  • Modifiez l'instanciation de fpga  de façon à ce que les paramètres HDISP et VDISP soient forcés à  160 et 90. Il s'agit une fois de plus de limiter, en simulation, le nombre de cycles à attendre de manière raisonnable tout en conservant en synthèse les valeurs par défaut.
  • Enfin modifiez la partie test (processus initial) de façon à
    • ne faire qu'un reset en début de simulation puis ne plus toucher au reset.
    • à garantir (choix de la position des switchs) que l'horloge à 27Mhz fonctionne pendant toute la simulation
    • à arrêter la simulation ($stop en fin de processus initial) lorsque l'on est sûr que 2 ou 3 images ont été générées (vous connaissez la taille de l'image ainsi que la fréquence pixel, d'où un arrêt au bout de xxx ms..).

8/ Première simulation 

Le fichier Makefile du répertoire fpga/simu a été mis à jour:

  • Il supporte la recherche de sources dans plusieurs répertoires
  • La simulation (vsim) fait appel à une bibliothèque ( option -L altera_lnsim_ver) de la société Altera qui contient des modèles de simulation précompilés tels que les modèles de PLL.
  • La compilation (vlog) précise les unités temporelles utilisées par Verilog (option -timescale "1ns/1ps") permettant aux PLL de se simuler correctement.

Lancez la simulation en mode graphique, visualisez les signaux "importants" (ceux de l'interface vga_if0) et tentez de vérifier "visuellement" le bon comportement de votre contrôleur vga. 

9/ Une vérification un peu plus complète.

Nous avons préparé pour vous un module screen "testeur" de signaux vga (sources dans vga/tb_src)). Ce testeur a deux fonctions:

  • Vérifier la validité des signaux de synchronisation en fontion du mode souhaité.
  • Créer un fichier contenant l'image générée par votre code

Modifiez tb_fpga de manière à instancier  le module screen connecté à l'interface vga . Cela devrait ressembler à quelque chose comme ceci 

 screen #(.mode(0),.X(160),.Y(90)) screen0(.vga_ifs(vga_if0))  ;

Le paramètre mode égal à 0 permet de sélectionner des paramètres de synchro d'images VGA 640x480 à 60Hz, les paramètres X et Y permettent d'ajuster la taille de l'image à quelque chose de plus raisonnable. Évidemment vous devez choisir les mêmes que ceux choisis pour instancier fpga

Relancez la simulation (le mode batch suffit) 

  • vous devriez voir apparaître des messages d'erreur explicites sur le non respect des valeurs attendues.
  • Si vous êtes chanceux, les seuls messages indiquent les générations successives d'images.

Après la simulation vous pouvez afficher la dernière image générée :

  • display work/screen_dump.ppm

Enfin, si vous faites évoluer la mire, et si vous simulez suffisamment de temps, vous pouvez afficher approximativement la séquence vidéo en lançant l'affichage en parallèle avec la simulation:

  • display -update 1 screen.ppm 

10/  Adaptation de la synthèse à la nouvelle version du code

Définitions des pattes d'entrée/sortie (fichier pins_assignement.tcl)

  • Nous avons préparé un fichier de contrainte spécifique pour les E/S VGA. Vous devez simplement modifier le fichier pins_assignement.tcl en ajoutant les lignes suivantes en fin de fichier
# Le E/S du contrôleur VGA
source pins_assignment_vga.tcl

 

Liste des fichiers pour la synthèse (fichier file_list.tcl)

  • Compléter le fichier file_list.tcl par les nouveaux fichiers source nécessaires au module vga:
    • Ce sont tous les fichiers .sv du répertoire vga/src (pas les fichiers de testbench)
    • Ils doivent être placés avant  le fichier fpga.sv
    • La définition de l'interface et de la pll doivent être placées avant la définition de vga

Définitions des contraintes de timing (fichier timing_constraints.sdc)

Notre système contient maintenant 3 horloges :

  • l'horloge à 50Mhz,
  • l'horloge à 27MHz
  • l'horloge  générée par la PLL.

Nous devons informer l'analyseur de timing de la fréquence des deux dernières horloges. Pour cela, nous pouvons demander à l'outil Quartus de dériver directement cette information des paramètres de la PLL.

  • Ajoutez les lignes suivantes au fichier timing_constraints.sdc.
# Ajout automatique des contraintes pour les PLL et les autres horloges dérivées
derive_pll_clocks -create_base_clocks

Enfin nous devons aussi indiquer que cette nouvelle horloge est indépendante des précédentes pour éviter que l'analyseur de timing cherche des contraintes là ou il n'y en a pas. Il faut modifier la commande "set_clock_groups" en ajoutant la nouvelle horloge. Malheureusement nous ne pouvons pas utiliser son nom (merci Quartus) , il faut utiliser un nom hiérarchique physique que l'on ne peut découvrir qu'après synthèse.

11/ Synthèse

  • Faites un "make clean" dans le dossier de synthèse du fpga.
  • Synthétisez votre design, corrigez les erreurs.
  • En ce qui concerne les Warnings, interrogez vos encadrants pour déterminer s'ils sont importants ou non.

Dans les messages, vous  devriez voir apparaitre une information ressemblant à cela:

Info (332111): Found 4 clocks
    Info (332111):   Period   Clock Name
    Info (332111): ======== ============
    Info (332111):   20.000     fpga_CLK
    Info (332111):   37.037 fpga_CLK_AUX
    Info (332111):    1.073 vga0|pll|altera_pll_i|general[0].gpll~FRACTIONAL_PLL|vcoph[0]
    Info (332111):   39.720 vga0|pll|altera_pll_i|general[0].gpll~PLL_OUTPUT_COUNTER|divclk

En dernière ligne, vous voyez apparaitre une horloge dont la période est celle de votre sortie VGA:

  • Récupérez (dans votre propre fichier de log) le nom hiérarchique complet de l'horloge (vga... | ..| ...divclk))
  • ajoutez-le à la liste des horloges qui doivent être exclusives.
  • Resynthétisez le design.

12/ Test sur la maquette

  • Programmez votre maquette (make program)
  • Vérifiez que les horloges à 50Mhz et à 27Mhz sont actives
  • Vérifiez que la maquette est connectée via un cable VGA à l'écran de votre PC et sélectionnez l'entrée analogique
  • Faites un reset éventuel de la maquete (bouton poussoir 0)
  • Et constatez le bon fonctionnement éventuel.
  • Si cela ne fonctionne pas, revérifiez toutes les étapes.

13/ Conclusion

  • Nous sommes maintenant prêt à stocker les images en mémoire (framebuffer) plutôt que de la générer à la volée. C'est l'objectif de l'étape suivante.

 

N'OUBLIEZ PAS ! : TAG GIT pour cette ETAPE : "VGA"

Fichier attachéTaille
PDF icon Circuit ADV7123364.75 Ko

É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

Étape 4: Un véritable bus avec un arbitre

ATTENTION : TAG GIT pour cette ETAPE : "ARBITRE"

1) Introduction

L'architecture réalisée n'est pas très réaliste. Dans la pratique, l'image affichée n'est pas stockée dans la SDRAM de manière statique mais générée par un dispositif extérieur tel qu'un microprocesseur ou un décodeur vidéo. L'accès au contrôleur SDRAM doit donc être partagé entre le bloc VGA et cet autre dispositif. Pour se rapprocher d'une telle structure nous allons mettre en place (dans fpga) l'organisation suivante:

  • Le contrôleur vga est inchangé.
  • Un module mire sera créé. Son seul port d'entrée sortie est une interface Wishbone. Son rôle est d'écrire une mire dans la mémoire SDRAM, et de répéter indéfiniment cette opération.
  • Le module fpga contiendra 3 interfaces Wishbones :

    • wshb_if_mire sera une interface dédiée aux transferts de données effectués par le module mire

    • wshb_if_vga sera une interface dédiée aux transferts de données du module vga

    • wshb_if_0 sera une interface dédiée aux transferts de données du module wb16_sdram16

  • L'accès au contrôleur de SDRAM étant maintenant partagé par deux modules totalement indépendants, il faut intercepter et arbitrer les requêtes des deux blocs. Un nouveau module wshb_intercon aura pour rôle de:
    • Détecter les requêtes en provenance des deux maitres (en se basant sur les signaux cyc des interfaces Wishbone).
    • Élire un maître.
    • Mettre en correspondance les signaux du maître élu avec les signaux de l'interface maître.

2) Travail à réaliser

  1. Créer un module mire chargé d'écrire une mire de manière permanente. Attention, le module mire devra être "fair-play" c'est-à-dire ne pas maintenir une demande d'écriture permanente de manière à permettre à l'arbitre de donner la main au module vga. Pour cela vous pouvez, par exemple, insérer un cycle à vide (avec cyc et stb à 0) toutes les 64 requêtes d'écriture.
  2. Modifiez (si ce n'est pas déjà le cas) le module vga pour que son signal cyc soit identique à stb. Ainsi, le module vga sera lui-même "fair-play". Quand sa fifo est pleine, il laisse le module mire prendre éventuellement la main sur le bus Wishbone.
  3. Créez le module wshb_intercon pour mettre en place le mécanisme d'arbitrage et de sélection des signaux. Pour garantir le bon fonctionnement de la vidéo, l'arbitre devra donner la priorité au contrôleur vga en cas de requêtes simultanées de mire et de vga.
  4. Modifiez enfin le module fpga pour mettre en place les nouvelles interfaces ainsi que les nouveaux modules
  5. Vérifiez par simulation le bon fonctionnement de l'ensemble
  6. Adaptez les fichiers pour la synthèse (file_list.tcl), synthétisez...
  7. Testez sur la carte...

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

Etape 5 : Enfin une séquence vidéo...

Pas de TAG pour cette étape.

 

0) Introduction

Cette dernière étape consiste à connecter le hps (Hard Processor System) du circuit FPGA à la matrice FPGA. Ainsi, les processeurs ARM du HPS pourront accéder au contrôleur de SDRAM de votre design et l'utiliser comme mémoire d'image (framebuffer). Nous avons mis en place un petit système Linux sur le HPS. Au démarrage il lance mplayer pour afficher une séquence vidéo via le framebuffer. 

L'essentiel du travail est donc d'adapter le module fpga afin d'une part de mettre le hps en place et d'autre part de le connecter à l'arbitre Wishbone à la place de la mire.

1) Mise à jour de l'environnement de travail

Placez-vous dans votre dépôt git personnel (cd)
Importez les code nécessaires à la mise en place du hps

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

Vous disposez d'une part d'un nouveau sous-répertoire hps et de quelques nouveaux fichiers dans les répertoires existants.

2) Adaptation du module fpga

Le hps correspond à un nouveau module (hps.sv) qui sera instancié dans le module fpga et devra communiquer avec le monde extérieur.

  •  Il faut, tout d'abord modifier les entrées/sorties de fpga.sv de manière à ajouter les entrées sorties nécessaires au hps. Ajoutez simplement la ligne suivante en début de liste des entrées-sorties du module fpga.
 // Partie hps
           `include "HPS_IO.svh"

Le fichier HPS_IO.svh situé dans le répertoire fpga/src contient simplement une série de déclaration de signaux....

  • Supprimez ou commentez l'instanciation du module mire dans le module fpga
  • Ajoutez une instance d'un module hps_block qui remplacera le module mire:
hps_block hps0(.wshb_ifm(wshb_if_mire),.*) ;

La syntaxe proposée indique que l'instance hps0 est :

  • d'une part connectée au bus wishbone dédiée à la mire 
  • d'autre part connectée à tous les signaux ayant des noms identiques à ses I/O (.*). 

Cette dernière syntaxe permet de connecter le hps aux entrées sorties que nous avons ajouté.

3) Simulation

Attention, en l'état votre FPGA n'est plus simulable. Il n'est pas du ressort de cette UE de simuler les processeurs ARM exécutant du code...

4) Synthèse 

  • Éditez le fichier file_list.tcl. Ajoutez les lignes suivantes en entête de fichier:
# Ajout du hps
if {$enable_hps} {
  set_global_assignment -name QIP_FILE     ${TOPDIR}/hps/hps/synthesis/hps.qip
  set_global_assignment -name VERILOG_FILE ${TOPDIR}/hps/hps/synthesis/hps.v
  set_global_assignment -name SYSTEMVERILOG_FILE ${TOPDIR}/hps/src/hps_block.sv
}
  • Editez le fichier pins_assignment.tcl. Ajoutez les lignes suivantes en fin de fichier:
# Le HPS
source pins_assignment_hps.tcl

Enfin modifiez le drapeau enable_hps concernant l'usage du processeur ARM dans le fichier syn_DE1-Soc_quartus.tcl

# On utilise les processeurs ARM... ou non
set enable_hps 1

 

  • Relancez une synthèse complète, vous avez intéret à tout nettoyer avant de démarrer:
    • make clean
    • make syn

ATTENTION:

  • La synthèse est relativement longue, il ne faut pas s'inquiéter
  • Il y a beaucoup de Warnings, il ne faut pas s'inquiéter.

Si tout ce passe bien, programmez votre fpga (pas besoin de précharger quoi que ce soit)

  • make program

Si tout va bien, vous avez une vidéo...., sinon voir vos encadrants...