Very High Speed Integrated Circuit Hardware Description Language/Interfaces VGA et PS2

Leçons de niveau 15
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Interfaces VGA et PS2
Icône de la faculté
Chapitre no 2
Leçon : Very High Speed Integrated Circuit Hardware Description Language
Chap. préc. :Introduction
Chap. suiv. :Interfaces RS232 et USB
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Very High Speed Integrated Circuit Hardware Description Language : Interfaces VGA et PS2
Very High Speed Integrated Circuit Hardware Description Language/Interfaces VGA et PS2
 », n'a pu être restituée correctement ci-dessus.

Nous allons dans ce chapitre nous intéresser aux interfaces VGA (Video Graphics Array) et PS/2 (Personnal System/2). Les cartes d'évaluations modernes ont pratiquement toutes ces deux interfaces directement montées et exploitables.

La partie concernant l'interface VGA sera émaillée de nombreux exemples destinés à appréhender quelques techniques modernes de réalisations de jeux vidéo. On partira de simples dessins de rectangles pour arriver aux jeux vidéo nécessitant un décor de fond sur lequel des sprites mobiles viennent se déplacer.

Interface VGA[modifier | modifier le wikicode]

L'interface VGA est intéressante car elle montre comment réaliser des signaux calibrés en temps à l'aide des compteurs. Un connecteur VGA montre que l'interface est complexe puisqu'elle gère 15 signaux différents. En ce qui nous concerne, nous allons nous intéresser à 5 signaux seulement définis dans la section suivante.

Les signaux à construire[modifier | modifier le wikicode]

La gestion d'un écran VGA se fait à l'aide de cinq signaux : trois pour les couleurs et deux pour la synchronisation. Dans un premier temps nous n'allons pas nous occuper des signaux de couleurs, autrement dit on ne s'intéressera qu'aux deux signaux de synchronisation.

Les signaux de synchronisation à générer sont décrits dans la figure ci-après pour une résolution de 640 x 480. Ils sont appelés "hsynch" pour la synchronisation horizontale et "vsynch" pour la synchronisation verticale. Ils sont simples et nécessitent seulement deux compteurs pour être réalisés.

Les signaux VGA avec du haut en bas : bleu vert rouge synchronisation horizontale et synchronisation verticale


Nous pouvons donc réaliser les deux signaux hsynch et vsynch à l'aide de l'architecture ci-dessous :

Parties matérielle pour gérer les synchronisations horizontales et verticales

Dans cette figure, les éléments séquentiels sont représentés en bleu turquoise et les éléments combinatoires en vert. Les comparateurs sont des éléments combinatoires en principe. Mais comme on peut le voir ici, on les synchronise sur des fronts d'horloge descendants. C'est du combinatoire mais synchronisé. Le programme VHDL capable de générer les signaux de synchronisation est donné un peu plus loin (solution de l'exercice 1). Le code VHDL correspondant permet simplement de synchroniser l'écran VGA mais ne dessine rien à l'intérieur.

Exercice 1[modifier | modifier le wikicode]

Écrire un programme VHDL qui implante l'architecture ci-dessus.

Voyons maintenant comment dessiner un rectangle.

Dessiner un rectangle[modifier | modifier le wikicode]

On cherche à dessiner un rectangle dont la position est donnée par des entrées externes. Sa taille est fixée une fois pour toute, seule sa position peut changer. Regardez la figure ci-dessous et vous verrez une partie combinatoire ajoutée, dans laquelle on entre les positions du rectangle sur 10 bits (ici x_rect et y_rect). Tout dessin sur l'écran se trouvera dans ce composant. Les coordonnées x_rect et y_rect seront à terme, fournies par un composant externe qui pour nous sera un processeur softcore.

Partie matérielle pour gérer les signaux de synchronisation horizontale et verticale et dessiner un rectangle

Dans ce schéma on a représenté en gris le composant de l'exercice 1. Le dessin explique de lui-même pourquoi on a ajouté dans la correction de l'exercice 1 des sorties appelées "pixel_row" et "pixel_column". Elles sont directement connectées au bloc combinatoire que l’on a ajouté à droite. Ce bloc permet de dessiner n’importe quoi à partir de la valeur des compteurs (justement "pixel_row" et "pixel_column") et ici des coordonnées d'une position d'un rectangle. Si les entrées "x_rect" et "y_rect" sont remplacées par d'autres données on peut imaginer dessiner n’importe quoi sur l'écran.

Début d’un principe
Fin du principe

Prenez toujours le temps de réfléchir à l'aspect fonctionnel des choses : quelles sont les entrées, quelles sont les sorties et que doit faire tel ou tel composant ? Cette réflexion peut prendre du temps, mais elle est absolument nécessaire pour une compréhension approfondie.

Exercice 2[modifier | modifier le wikicode]

On dispose d'un composant combinatoire rect capable de dessiner un rectangle décrit en VHDL par son entité :

library IEEE;
use IEEE.STD_LOGIC_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
ENTITY rect IS PORT(
  row,col,x_rec,y_rec,delta_x,delta_y :in STD_LOGIC_VECTOR(9 DOWNTO 0);
  colorRGB : in STD_LOGIC_VECTOR(2 DOWNTO 0);
  red1,green1,blue1 : out std_logic);
END rect;

Sa couleur est fixée par son entrée coloRGB, sa position par x_rect et y_rect. Sa taille est fixée par delta_x et delta_y. Le code correspondant à son architecture est :

ARCHITECTURE arect of rect is begin
  PROCESS(row,col,x_rec,y_rec) BEGIN
    if row > y_rec and row < y_rec+delta_y then
      if col >x_rec and col < x_rec+delta_x then
               red1 <= colorRGB(2);
               green1 <= colorRGB(1);
               blue1 <= colorRGB(0);
      else
               red1 <= '0';
               green1 <= '0';
               blue1 <= '0';
      end if;
   else
                red1 <= '0';
               green1 <= '0';
               blue1 <= '0';
    end if;
  end process;
end arect;

1°) Écrire le programme VHDL qui permet de dessiner un rectangle de taille 100x100 aux coordonnées 100 suivant x et 100 suivant y. On dispose pour cela d'une horloge à 50 MHz appelée clk_50. On réalise donc l'assemblage de la figure ci-dessus pour donner l'entité :

ENTITY VGAtop IS
  PORT (clk_50 : in STD_LOGIC;
          hsynch,vsynch,red,green,blue : out STD_LOGIC);
END VGAtop;

2°) Modifier le programme précédant pour qu’il dessine une balle et deux raquettes. La position de la balle sera donnée par des entrées externes mais les positions X des raquettes seront fixées.

ENTITY VGAtop IS
  PORT (clk_50 : in STD_LOGIC;
        x_rect, y_rect: IN STD_LOGIC_VECTOR(9 DOWNTO 0);   
        y_raquG, y_raquD: IN STD_LOGIC_VECTOR(7 DOWNTO 0); 
        hsynch,vsynch,red,green,blue : out STD_LOGIC);
END VGAtop;

Remarquez par la même occasion que l’on a choisi les positions verticales des raquettes sur seulement 8 bits. Économie économie !

Pour ne pas rester cantonné au fondeur Xilinx, nous allons maintenant nous intéresser au même problème avec Altera.

Dessiner un rectangle sur DE2-115 (Altera)[modifier | modifier le wikicode]

Voici un exemple de tracé de rectangle destiné à la carte DE2-115 d'Altera. La grande différence avec ce qui a été fait jusqu'à présent est l'apparition des signaux VGA_BLANK, VGA_SYNC et VGA_CLK en sortie. Ils sont liés d'une manière ou une autre au fait que cette carte utilise des convertisseurs numériques analogiques pour les signaux de couleur.

Les contraintes pour la carte DE2-115 sont présentées sous forme de fichier csv. Ce genre de fichier peut facilement s'importer dans Quartus.

# Quartus II 64-Bit Version 15.0.0 Build 145 04/22/2015 SJ Web Edition
# File: /home/royer001/Bureau/pong.csv
# Generated on: Fri Oct 23 09:03:45 2015

# Note: The column header names should not be changed if you wish to import this .csv file into the Quartus II software.

To,Direction,Location,I/O Bank,VREF Group,I/O Standard,Reserved
clk_50,Input,PIN_Y2,2,B2_N0,3.3-V LVTTL,
blue,Output,PIN_D12,8,B8_N0,3.3-V LVTTL,
VGA_BLANK,Output,PIN_F11,8,B8_N1,3.3-V LVTTL,
VGA_CLK,Output,PIN_A12,8,B8_N0,3.3-V LVTTL,
green,Output,PIN_C9,8,B8_N1,3.3-V LVTTL,
hsynch,Output,PIN_G13,8,B8_N0,3.3-V LVTTL,
red,Output,PIN_H10,8,B8_N1,3.3-V LVTTL,
VGA_SYNC,Output,PIN_C10,8,B8_N0,3.3-V LVTTL,
vsynch,Output,PIN_C13,8,B8_N0,3.3-V LVTTL,

Dessiner un ensemble de rectangles[modifier | modifier le wikicode]

On désire toujours rester dans le domaine du dessin de rectangles, mais en cherchant à dessiner des objets plus complexes, comme des afficheurs sept segments. Si l’on désire en plus pouvoir modifier la taille de ces objets, il faut concevoir l’ensemble avec intelligence. En effet modifier la taille implique un ensemble de calculs. Ces calculs ne doivent en aucun cas être réalisés par le VHDL mais par le compilateur avant implantation. Pour se faire on dispose d'une possibilité avec le generic, et voici comment cela fonctionne.

Soit le programme VHDL suivant :

ENTITY rect_generic is 
	generic(x,y,dx,dy : natural); --c'est ici qu’il y a du nouveau
	port(vgax, vgay : in std_logic_vector(9 downto 0);
		 RGB : in std_logic_vector(2 downto 0);
		 e_rect : in std_logic;
		 s_red, s_blue, s_green : out std_logic);
END rect_generic;

ARCHITECTURE arect_gen OF rect_generic IS
  BEGIN
	PROCESS (vgax, vgay, e_rect) 
		BEGIN
			IF vgax>x AND vgax<x+dx THEN
				IF vgay>y and vgay<y+dy THEN
					IF e_rect = '1' THEN
						s_red <= RGB(0);
						s_green <= RGB(1);
						s_blue <= RGB(2);
					ELSE
						s_red <= '0';
						s_green <= '0';
						s_blue <= '0';
					END IF;
				ELSE
					s_red <= '0';
					s_green <= '0';
					s_blue <= '0';
				END IF;
			ELSE
				s_red <= '0';
				s_green <= '0';
				s_blue <= '0';
			END IF;
	END PROCESS;
END arect_gen;

Ses seules grandes différences sont l'apparition dans l'entité de quatre valeurs generic : x, et y pour la position et dx, dy pour la taille ainsi que l'entrée "e_rect" destinée à choisir un affichage ou non du rectangle. L'utilisation d'un tel rectangle se fera toujours par un "port map", mais il faudra lui ajouter un "generic map".

Exercice 3[modifier | modifier le wikicode]

Question 1[modifier | modifier le wikicode]

Écrire un programme VHDL réalisant un afficheur sept segments à partir du rectangle générique ci-dessus. L'ensemble devra être caractérisé par une position en x et en y ainsi qu'une taille. L'entrée se fera sur 4 bits.

Question 2[modifier | modifier le wikicode]

Réaliser un ensemble capable d'afficher deux raquettes, une balle, un rectangle de séparation en bas de l'écran et un affichage de deux scores sur deux digits. Vous remarquerez sur la figure ci-dessous le nombre impressionnant de fils d'entrées regroupés en bus... d'où la question : que va-t-on bien pouvoir faire de tous ces fils ? Ils seront reliés à un processeur par l'intermédiaire de PORTs tout simplement :

Voici la figure correspondante :

Gestion du jeu de Pong sur un écran VGA

On montre sur la figure ci-dessus ce que l’on cherche à réaliser. Encore une fois il est important de se poser devant celle-ci et de bien regarder quelles sont les entrées et quelles sont les sorties ? Les sorties sont les 5 signaux VGA non représentés qui en final vont faire le dessin ci-dessus, c'est-à-dire deux raquettes, une balle et quatre afficheurs de scores. Pour cadencer tout cela il faut une horloge, encore une fois non présentée. Toutes les entrées sont pour changer les positions, de la balle et des raquettes, et pour fournir des nombres à afficher sur les scores.

Question 3[modifier | modifier le wikicode]

Modifier l'exemple de la question 2 pour réaliser la figure ci-dessous.

Écran VGA définissant notre casse brique

Vu le nombre d'entrées de ce composants il ne peut qu'être utilisé avec un processeur embarqué :

Nous allons maintenant nous intéresser à un autre problème : comment afficher des caractères sur un écran vidéo ?

Dessiner des caractères sur un écran[modifier | modifier le wikicode]

Nous allons nous intéresser dans cette section à l’affichage de caractères sur un écran V.G.A.. Depuis les débuts de l'informatique on utilise une ROM où l’on stocke les formes des caractères pour faire ce travail. Nous allons donc commencer à présenter la ROM de caractères que nous allons utiliser avec un passage obligé à un peu de terminologie.

ROM/RAM de caractères[modifier | modifier le wikicode]

Nos investigations sur le sujet nous ont donné l’occasion d’utiliser deux ROMs différentes :

  • celle de P. P. Chu associée à son livre "FPGA prototyping by VHDL Examples"
  • celle d'un projet libre chez Opencores appelé Yet Another VGA.

Après divers essais nous avons décidé d’utiliser la deuxième car elle donne de plus beaux résultats pour un grossissement de 2x2... mais comme on dit les goûts et les couleurs...

La mise en œuvre de mémoires programmes pour nos processeurs nous a appris à aborder les mémoires avec précautions. Tous les F.P.G.A. modernes comportent des zones réservées à ces mémoires : on les appelle des BRAM. Si une option de compilation correcte manque, une synthèse utilisant ce que l’on appelle des mémoires distribuées sera réalisée. Ce qui est en général très inefficace. Pour éviter tout problème, nous avons décidé d’utiliser les modèles Xilinx qui forcent la synthèse avec les BRAM. Mais cela revient à changer le code source car ces modèles BRAMs ont leurs propres méthodes d'initialisation et malheureusement ce nouveau code est beaucoup moins portable ! Nous allons quand même publier le code correspondant ici, mais il vous faudra l'adapter pour les autres fondeurs comme Altera (voir remarque ci-dessous) :

Ce code nécessite la remarque suivante :

Un travail correctement réalisé devrait utiliser une RAMB16_S1 plutôt qu'une RAMB16_S9 : cela éviterait le multiplexeur de sortie ! (Ne vous inquiétez pas si vous ne voyez pas de quel multiplexeur nous parlons puisqu'on va le retirer). Voici la BRAM correspondante, que nous utiliserons dans la section suivante.

Cette RAM sera modifiée lorsque l’on s'intéressera au jeu du pacman un peu plus loin.

Utilisation de la BRAM[modifier | modifier le wikicode]

S'il est important de posséder une BRAM de caractères il est encore plus important d’en voir le contenu sur un écran VGA. C'est à ce travail que nous allons nous consacrer maintenant.

Cahier des charges simplifié pour commencer[modifier | modifier le wikicode]

Afin de visualiser le contenu de notre ROM de caractères, nous désirons dessiner 4 lignes de 32 caractères dans le coin gauche de l'écran. Voici en image ce que cela donne. On a représenté les caractères non pas par leur contenu mais par des pavés numérotés.

Comment retrouver le numéro des 128 pavés dessinés sur l'écran.

Regardez attentivement cette figure : elle vous explique comment retrouver les numéros des pavés dessinés en haut à gauche de l'écran VGA. Ces numéros sont importants car ils représentent les codes ASCII des caractères que l’on dessinera. Puisque le dessin présente des pavés numérotés de 0 à 127, cela confirme que notre ROM de caractères contient 128 caractères.

  • ce qui est au-dessus de l'écran représente quelques valeurs binaires spécifiques de pixel_x (ou pixel_column). Avec un peu d'imagination vous devinez comment ce compteur varie quand on se déplace vers la droite...
  • ce qui est à gauche de l'écran représente quelques valeurs binaires spécifiques de pixel_y (ou pixel_row). Avec un peu d'imagination vous devinez comment ce compteur varie quand on se déplace vers le bas...
  • le registre de 7 bits en bas est composée d'une partie bleue tirée de pixel_y (2 bits) et d'une partie jaune tirée de pixel_x (4 bits)
  • si vous avez compris vous verrez assez facilement qu’à droite du pavé 31 il y a un pavé 0 non représentée car on désire ne pas le voir. Ce que nous essayons d'expliquer ici, c’est que la mise bout à bout de pixel_y(5:4) et pixel_x(7:3) donne un numéro au pavé à droite pavé 31. Un autre mécanisme sera nécessaire pour effacer ce pavé !
Principe de réalisation[modifier | modifier le wikicode]

Nous allons maintenant chercher une architecture générale capable de dessiner ces caractères. La voici en image :

Comment dessiner les 128 caractères sur l'écran.

L'inverseur est uniquement présent pour éviter de dessiner les caractères à l’envers (réflexion selon un axe vertical au milieu du caractère). On pourrait le supprimer en initialisant autrement la ROM, ce que nous n'avons pas le courage de faire.

Début d’un principe
Fin du principe

Nous pouvons maintenant passer au code source.

Solution[modifier | modifier le wikicode]

Voici le programme VHDL complet qui réalise ceci :

Vous avez trois lignes (utilisez les commentaires pour cela) à changer pour retirer le zoom :

  • char_addr <= pixel_y(6 downto 5) & pixel_x(8 downto 4); à remplacer par char_addr <= pixel_y(5 downto 4) & pixel_x(7 downto 3);
  • rom_addr <= char_addr & pixel_y(4 downto 1) & not pixel_x(3 downto 1); à remplacer par rom_addr <= char_addr & pixel_y(3 downto 0) & not pixel_x(2 downto 0);
  • '1' when (pixel_x(9)='0') and (pixel_y(9 downto 7)="000") else à remplacer par '1' when (pixel_x(9 downto 8)="00") and (pixel_y(9 downto 6)="0000") else

Avant de passer à la suite nous vous proposons un exercice d'application qui représente une petite modification par rapport à ce que l’on vient de présenter. Le résoudre par vous-même vous montrera que vous avez bien compris la génération de caractères.

Exercice 4[modifier | modifier le wikicode]

Puisque notre but, même s'il n’est pas encore avoué, est de remplacer la partie basse de la question 3 de l'exercice 3 par un ensemble de caractères, nous vous proposons le cahier des charges suivant :

  • dessiner 32 caractères en bas de l'écran représentés par leur numéros dans le schéma ci-dessous
  • utiliser encore le zoom 2x2
Dessiner 32 caractères en bas de l'écran.

On rappelle que ce qui sera affiché à l'écran n’est pas le numéro des pavés mais les caractères dont le code ASCII est le numéro de pavé, soit ici la ligne : @ABCDEFGHIJKLMNOPQRST[\]^_

Les questions qu’il nous faut résoudre sont donc :

  • Comment générer les numéros des pavés à partir de pixel_x et de pixel_y ?
  • La méthode simple utilisée dans l'exemple précédent (concaténation d'une partie de pixel_x avec une partie de pixel_y) fonctionne-t-elle encore ?
Afficher un texte quelconque[modifier | modifier le wikicode]

Jusqu'à présent les numéros des pavés à afficher étaient successifs. Mais qu'en est-il si l’on désire afficher une chaîne de caractères fixe quelconque ?

Ce problème est généralement résolu en utilisant une deuxième mémoire contenant la liste des codes ASCII à afficher. Puisque vous avez maintenant un peu l'habitude de la terminologie Xilinx vous devinez qu'une RAMB16_S9 sera nécessaire.

Il est facile de décomposer ce problème en deux parties :

  • générer des pavés de la figure de l'exercice 4 portant les numéros 0, 1, ..., 31, 32, 33, .. 39 pour le bas de l'écran (au lieu des 64 à 95 dans la figure).
  • utiliser les numéros précédemment générés comme adresse de la mémoire qui contient les codes ASCII.

Nous allons donc commencer par essayer de générer des pavés numérotés à partir de 0. Ils formeront les adresses de la RAM des caractères à afficher. Si l'adresse se nomme addr_pave (pour adresse pavé ou numéro pavé) on réalise simplement :

addr_pave <= pixel_y(6) & pixel_x(9 downto 4);

Quand pixel_x et pixel_y s'incrémentent addr_pave donne le numéro de chaque pavé. Voyez-vous correctement que la dernière ligne (de caractères sur l'écran VGA) commence par le numéro 64 et s'arrête à 103 ?

Voila, nous sommes prêts à ajouter la RAMB16_S9. Avant cela il nous faut pointer une petite subtilité qu’il nous faudra aussi résoudre.


Exercice 5[modifier | modifier le wikicode]

Réaliser l’ensemble capable d'afficher la chaine de caractères "GEII-TROYES SCORE : 0000 - LEVEL : 00" tout en bas de l'écran. En zoom 2x2, il y a juste la place. Il vous faudra correctement initialiser la RAMB16 et surtout au bon endroit.

Voici un schéma de principe qui explique ce que l’on cherche à réaliser :

Comment réaliser l’affichage du texte

Notez la présence de deux buffers (en jaune pâle) destinés à synchroniser le dessin à l'écran avec le balayage, sachant que l’on utilise deux mémoires synchrones. Pourquoi deux ? Parce qu'on utilise le 50 MHz alors que les compteurs de balayages sont en 25 MHz. Cela mériterait certainement bien d'autres explications, mais nous remettons celles-ci à plus tard.

Voici maintenant la solution complète en VHDL.

Question subsidiaire : comment changer le code source précédent pour que le numéro du pavé de la dernière ligne commence par zéro (au lieu de 64) pour que le texte à afficher se trouve en adresse 0.

Conclusion[modifier | modifier le wikicode]

Ces essais nous ont montré un léger problème dans notre module de synchronisation VGA car les caractères les plus à gauches ne sont dessinés que partiellement. Jusqu'à présent nous avons toujours mis cela sur le compte des écrans mais le problème est qu'avec les fichiers sources de P. P. Chu associés à son livre, on n'a pas ce problème !

Nous avons donc corrigé notre fichier de synchronisation dans l'exercice 1 pour éviter ce problème.

Mélanger dessins et caractères[modifier | modifier le wikicode]

Arrivé ici vous êtes capable d’une part de dessiner des rectangles et d’autre part d'afficher des caractères. Nous allons nous intéresser au mélange des deux maintenant.

Exercice 6[modifier | modifier le wikicode]

Reprendre la question 3 de l'exercice 3 en retirant toute la partie basse composée de deux afficheurs sept segments et en la remplaçant par le texte de la section précédente. À ce point, vous n'avez aucune idée sur la façon qu’il vous faudra mettre en œuvre pour afficher le bon niveau (level en anglais). Il vous faudra donc retirer l'entrée correspondante. Elle est d'ailleurs improprement appelée score alors qu'en réalité elle est utilisée pour afficher le niveau (voir le projet étudiant correspondant et le programme C de la correction).

Nous allons mettre toutes ces connaissances acquises à la lecture de ces sections à profit pour réaliser un pacman. Cela nous permettra de changer un peu de domaine de jeu pour laisser tomber les raquettes.

Pacman[modifier | modifier le wikicode]

Les techniques graphiques utilisées dans le jeu de pacman sont universelles. Elles n'ont cependant pas été utilisées jusqu'à présent même si le jeu de Casse-briques aurait très bien pu les mettre en œuvre (ce qui sera d'ailleurs essayé dans des futurs projets).

Ces techniques peuvent être résumées comme ceci :

  • vous disposez d'un fond d'écran qui joue le rôle de décor. Traditionnellement il est réalisé avec un pavage dans lequel sont dessinés diverses briques de taille constante. Ce décor est en principe fixe. Cette technique est complètement identique au dessin de caractères déjà évoquée dans ce chapitre.
  • vous disposez d'un ou plusieurs objets mobiles que l’on appelle sprite (terme anglo-saxon qui ne semble pas avoir été traduit. Ces sprites ont une partie transparente et une partie opaque.

Si l’on reprend le jeu de casse-briques, l’affichage du score et des briques constitue le décor et la (ou les) raquette(s) ainsi que la balle constituent les sprites. Jusqu'à maintenant nous avons utilisé la technique de dessin de rectangles pour les sprites. Le décor quant à lui était un mix : rectangles pour les briques et pavés pour les scores (à partir de 2012).

Construction du décor[modifier | modifier le wikicode]

Nous allons utiliser la technique des dessins de caractères pour construire le décor. Puisque nous disposons d'une ROM de caractères 8x16 (horizontal x vertical), nous allons l’utiliser telle quelle. Une utilisation de briques 16x16 aurait certainement été bien mieux, esthétiquement parlant, mais nous remettons cela à plus tard, dans un futur projet.

Le décor est changeant : les Pac-gommes disposées tout au long du labyrinthe sont mangées par pacman et devront donc disparaitre. Conséquence, le décor doit avoir une image en RAM. Comme déjà indiqué, la première chose à faire dans ce cas précis, est de choisir le nombre et la taille des pavés numérotés que l’on va disposer à l'écran. Ces numéros (de pavé) constitueront l'adresse de la dite RAM.

Construction des numéros de pavés[modifier | modifier le wikicode]

Nous allons tout reprendre les explications depuis le début. Il nous faut donc construire un nouveau pavage. Nous allons choisir des pavés 16x32 (horizontal x vertical). On y placera à l'intérieur des pavés de taille 8x16 qui seront donc grossis d'un facteur 2.

Comment retrouver le numéro des pavés dessinés sur l'écran.

Nous rappelons dans cette figure comment il est simple de former les numéros de pavés à l'aide d'une simple concaténation. Ce faisant, les numéros des pavés dépassent la taille de l'écran vers la droite puisqu’il vont jusqu'à 63 pour la première ligne. Ceci est visible dès la seconde ligne qui commence par le numéro 64.

Ces numéros de pavés vont nous servir à adresser une mémoire qui contiendra donc l’ensemble du décor. Le fait que certains des pavés ne soient pas visibles nous fait perdre de la place dans la mémoire, mais ceci est compensé par la facilité de réaliser les adresses (nous n'insisterons jamais assez, par simple concaténation).

Construction des pavés de base du décor[modifier | modifier le wikicode]

À part le texte qui sera affiché en bas de l'écran, nous n'avons pas besoin de beaucoup de pavés pour fabriquer un labyrinthe. Les voici présentés :

Nos pavés de base

Il faut savoir que nous avons décidé de ne pas coder les couleurs dans la mémoire. Les couleurs de cette figures sont celles que nous avons choisies mais elles seront obtenues par un circuit combinatoire assez complexe. Les numéros entre parenthèses représentent l'adresse où se trouve le dessin du caractère.

Cette figure permet de comprendre le début de l'initialisation de la mémoire :

--*********** début du contenu de la RAMB16_S1
-- rien d'intéressant / Noir complet
INIT_00=>X"00000000183C7F78786F3C180000000000000000000000000000000000000000",
--rien d'intéressant ici
INIT_01=>X"00000000183C6E7E7E7E3C180000000000000000FF0000FF0000FF0000FF0000",
-- fruit / pac-gomme
INIT_02=>X"0000000000183C7E7E3C180000000000000000000000183C3C18000000000000",
--brique / rien d'intéressant
INIT_03=>X"FFD5ABD5ABD5ABD5ABD5ABD5ABD5ABFF00005555555555555555555555550000",

Cette mémoire est commandée par une RAMB16_S9 qui sera destinée à réaliser le labyrinthe complet.

Exercice 7[modifier | modifier le wikicode]

À partir du contenu de la RAMB16_S9, pouvez-vous dessiner ce qui sera reproduit sur votre écran VGA ?

-- Address 0 to 4095
INIT_00=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_01=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_02=>X"0707070707070707070707070707070707070707000707070707070707070700",
INIT_03=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_04=>X"0004000400040004000400040004000400040004000400040004000400040700",
INIT_05=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_06=>X"0407040704070407040704070407070707070700000707070404070400040700",
INIT_07=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_08=>X"0407040704070400040704000407000000000000000000070407070707040700",
INIT_09=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_0a=>X"0407040404070407040704070407000000000000000000070407070407040000",
INIT_0b=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_0c=>X"0404040704070400040704000407070707070707070707070400000400040700",
INIT_0d=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_0e=>X"0407040704000407040704070004000400040004000400040704040704070700",
INIT_0f=>X"0000000000000000000000000000000000000000000000000000000000000007",
-- Address 4096 to 8191
INIT_10=>X"0404050404070707040404040704070704070407070004000407040004040700",
INIT_11=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_12=>X"0407040704070407040704070407040004000407040704070407070407040000",
INIT_13=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_14=>X"0404040400040004000400040004000400040004000400040004000400040700",
INIT_15=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_16=>X"0707070707070707070707070707070707070700070707070707070707070700",
INIT_17=>X"0000000000000000000000000000000000000000000000000000000000000007",
INIT_18=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_19=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1a=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1b=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1c=>X"56454C202D203030303030203A2045524F435320205345594F52542D49494547",
INIT_1d=>X"000000000000000000000000000000000000000000002020203030203A204C45",
INIT_1e=>X"0000000000000000000000000000000000000000000000000000000000000000",
INIT_1f=>X"0000000000000000000000000000000000000000000000000000000000000000",

Une chaîne de caractères est écrite en bas de l'écran. Pouvez-vous la donner en vous aidant des codes ASCII contenu dans cette mémoire.


Construction et gestion des sprites[modifier | modifier le wikicode]

Les sprites sont des parties mobiles. Leur dessin sera stocké lui aussi dans la ROM de caractères. Étant donné qu'un sprite se superpose au décor qui lui aussi utilise la ROM de caractères, cela indique qu’à un certain moment cette ROM de caractères peut être accédée deux fois simultanément ! pour le décor du fond et pour le sprite ! Il en résulte immédiatement qu’il nous faudra ajouter un port à la ROM de caractères. D'autre part, il nous faudra gérer deux sprites : le pacman et l'ennemi et choisir une priorité puisque les mémoires contenant plus de deux PORTs n'existent pas.

Exercice 8[modifier | modifier le wikicode]

Nous donnons le contenu de la RAMB16_S1 qui gère les sprites :

-- Address 0 to 4095
--??/blanc
INIT_00=>X"00000000183C7F78786F3C180000000000000000000000000000000000000000",
--INIT_01=>X"0000242424242424242424242424000000000000FF0000FF0000FF0000FF0000",
INIT_01=>X"00000000183C6E7E7E7E3C180000000000000000FF0000FF0000FF0000FF0000",
-- fruit/pac-gomme
INIT_02=>X"0000000000183C7E7E3C180000000000000000000000183C3C18000000000000",
--brique/??
INIT_03=>X"FFD5ABD5ABD5ABD5ABD5ABD5ABD5ABFF00005555555555555555555555550000",
--pacman gauche/droite
INIT_04=>X"00000000183CFE1E1EF63C1800000000000000003078FEF0F0DE783000000000",
--pacman down/up
INIT_05=>X"00000024243C6E7E7E7E3C180000000000000000183C7E7E7E6E3C2424000000",
--pacman fermé gauche/droite
INIT_06=>X"00000000183C7E7E7E763C1800000000000000003078FCFCFCDC783000000000", 
-- pacman fermé down/up
INIT_07=>X"00000000183C6E7E7E7E3C180000000000000000183C7E7E7E6E3C1800000000",
-- ennemi fantome/ennemi esprit
INIT_08=>X"000000003C243C007E5A7E0000000000000000AAFFC3C3FF7E5A7E3C00000000",

Pouvez-vous dessiner sur une feuille ce que cela donnera sur un écran VGA ?

Affichage des sprites[modifier | modifier le wikicode]

Pour gérer les sprites il nous faut prévoir les entrées correspondantes dans l'entité. La voici présentée :

entity pacman is
   port(
      clk, reset: in std_logic;
		-- pacman's positions 
		pacman_x,pacman_y : in std_logic_vector(9 downto 0);
		-- numéro du sprite pacman utilisé (huit possibles)
		pacman_sprite_number : in std_logic_vector(2 downto 0);
		-- enemy's positions 
		enemy_x,enemy_y : in std_logic_vector(9 downto 0);
		-- numéro du sprite ennemi utilisé (deux possibles)
		enemy_sprite_number : in std_logic;
      hsync, vsync: out  std_logic;
      rgb: out std_logic_vector(2 downto 0)
   );
end pacman;

On y voit apparaître les coordonnées du pacman sur 10 bits et le numéro du sprite car le pacman est représenté bouche ouverte ou fermée et allant vers la droite, la gauche, le haut et le bas. Cela fait en tout 8 sprites différents d'où les trois bits "pacman_sprite_number". Le fait que les coordonnées soient sur 10 bits indique qu’il peut être affiché n’importe où sur l'écran. Si l’on désire interfacer ces coordonnées avec un processeur 8 bits, on peut se limiter à 8 bits et relier directement les deux bits de poids faibles à 0.

Le programme complet[modifier | modifier le wikicode]

Voici le programme complet avec sprite et décor.

Ce programme est un peu long, mais nous sommes obligé de le donner quasiment complètement puisque le contenu de la RAMB16_S1 a changé et en plus elle a été transformée en RAMB16_S1_S1.



Ressources[modifier | modifier le wikicode]

Pour une gestion plus avancée des cartes VGA :


Passons maintenant à un tout autre sujet, la lecture d'un clavier PS/2.

Les claviers PS/2[modifier | modifier le wikicode]

Le protocole PS/2 est un protocole bidirectionnel du périphérique vers l'hôte. Nous limiterons notre discussion ici en considérant le périphérique comme un clavier. Il peut sembler à première vu que la communication entre un clavier et un hôte est mono directionnelle (clavier --> hôte), mais il n'en est rien.

Début de l'exemple
Fin de l'exemple

Comprendre le protocole PS/2 nécessite ainsi de s'interroger sur les deux sens de communication :

  • clavier vers hôte
  • hôte vers clavier

La figure ci-après vous montre un exemple de protocole PS/2 avec ces appellations.

Le protocole PS/2[modifier | modifier le wikicode]

C'est le système maître qui alimente le périphérique PS/2. Les signaux "ps2_data" et "ps2_clk" sont bidirectionnels et à sortie collecteur ouvert. Une résistance de 10K ohm environ doivent être placées entre ces deux signaux et l'alimentation ce qui garantie un niveau haut hors de toute transaction.

Les octets de commandes et messages sont transmis de façon synchrone et série sur 11 bits ( les 8 bits à transmettre, poids faible en premier, précédés d'un bit start (0) et suivi d'un bit de parité et d'un bit stop (1). Le bit de parité impaire vaut 1 si le nombre total de 1 dans l'octet et le bit de parité lui-même est impair. Durant la transmission, c’est le périphérique qui fournit l'horloge en la positionnant à un niveau bas (front descendant) tandis que l'émetteur (système ou périphérique) place data à un niveau bas pour un bit 0 ou le laisse inactif pour un bit 1.

La fréquence d'horloge est au maximum de 33 kHz mais le plus couramment de 15 kHz.

Entre les transmissions, le bus peut être :

  • Idle : CLK et DATA sont tous les deux au niveau haut . Il n y a pas d'activité.
  • Inhibit : Le système maintient CLK au niveau bas.
  • Request to send : Le maître maintient DATA au niveau bas mais laisse CLK flotter. Il est prêt à émettre.

Chronogrammes de l'échange clavier vers hôte[modifier | modifier le wikicode]

Puisque c’est le clavier qui est à l'initiative de l'échange des données, c’est lui qui aura la lourde tâche de la fabrication de l'horloge. De l'autre côté, l'hôte échantillonnera les données sur les fronts descendants de cette horloge comme indiqué dans le chronogramme ci-dessous :

Le protocole PS/2

On remarque que le clavier envoie d’abord un bit de "start" à '0', puis les données sur 8 bits en commençant par le poids faible, puis un bit de parité suivi d'un bit de stop qui est '1'. Ceci est bien conforme au protocole PS/2 décrit dans la précédente section.

Exercice 7[modifier | modifier le wikicode]

Imaginez une architecture électronique capable de lire le protocole PS/2.

Echange hôte vers clavier[modifier | modifier le wikicode]

À continuer

Implémentation[modifier | modifier le wikicode]

Un ensemble de TPs permet de comprendre le PS2 :

Faites les exercices en commençant par Travail_pratique/TP 2.

Cette série de TPs construit pas à pas un module capable de réaliser la lecture du PS2.

La fin du TP3 (particulièrement la mise en conformité des horloges) montre des implantations directement utilisables et beaucoup plus compactes que celles construites de toute pièce.

Pour dire les choses autrement, si vous désirez comprendre le PS2 faites les TPs proposés, si vous chercher un module tout prêt à être utilisé allez directement à la mise en conformité des horloges et prenez le code VHDL correspondant.

En ce qui nous concerne, nous avons expérimenté :

  • les TPs (qui n'étaient bien sûr pas corrigés à l'époque) avec des étudiants de deuxième année de DUT GEII (L2 = niveau 15)
  • un interfaçage entre le module de P.P Chu et le picoBlaze avec des étudiants de branche de l'UTT (L3) énoncé des TPs Printemps 2012
  • un interfaçage entre le module de P.P Chu et l'ATMega8 avec des étudiants de branche de l'UTT (L3) énoncé des TPs Printemps 2013

Voir aussi[modifier | modifier le wikicode]