É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