La raquette du Pong sur FPGA
Ce billet est la suite logique… Après la prise de contrôle du port VGA et le décodage des signaux en quadrature d’un encodeur rotatif, nous contrôlons les horizontales et les verticales. Nous pouvons vous noyer sous un millier de chaînes ou dilater une simple image jusqu’à lui donner la clarté du cristal, et même au-delà…1
Euh, non… Plus modestement, je commence un Pong. C’est tellement original comme idée, je n’ai pas pû y résister. Je commence un Pong, mais j’y vais doucement. Dans ce billet, je vais animer la raquette du Pong et ce sera déjà bien, si.
Architecture du projet
Les différents blocs :
pll
: un circuit spécialisé disponible dans la bibliothèque de composants de Quartus Prime Lite (IP Catalog → Library → Basic Functions → Clocks, PLLs and Resets → PLL → ALTPLL), une boucle à verrouillage de phase ou PLL (phase-locked loop) pour asservir la fréquence de sortie sur un multiple de la fréquence d’entrée. L’entrée du bloc est raccordée à l’horloge principale 50MHz de la carte FPGA (entréeCLOCK_50
). Le ratio de fréquence est fixé à 63/125 pour réduire la fréquence de l’horloge principale : (63/125) x 50 = 25,2MHz. Cette fréquence est nécessaire pour produire une vidéo VGA 640x480 à 60 images par seconde ;freq_div
: un diviseur de fréquence programmé en systemVerilog. Quelques kHz suffisent pour les signaux très lents d’un encodeur rotatif actionné à la main. Une fréquence trop élevée, et l’on pourrait échantillonner des parasites ou les rebonds des contacts mécaniques de l’encodeur.encoder
: le module qui décode les signaux en quadrature DT et CLK de l’encodeur rotatif. La sortiedir[1..0]
vaut +1 ou -1 selon le sens de rotation, et 0 s’il n’y a pas de mouvement détecté.encoder_pulse
: le problème avec le module précédent est qu’il produit un signal selon la rotation de l’encodeur (dir[1..0]
= +1, -1 ou 0) à une fréquence très lente. Ce signal peut être asynchrone par rapport à l’horloge rapide pour gérer l’affichage VGA (25.2MHz), ce qui va poser des problèmes de synchronisation. Ce moduleencoder_pulse
produira une impulsion brève à chaque détection de mouvement, active pendant un seul cycle à 25.2MHz, et qui pourra être exploitée facilement dans d’autres modules qui gèrent le VGA.vga_sync
etdrawing
: les modules qui produisent les signaux VGA, avec le dessin de la raquette qui se met à jour en fonction de la rotation de l’encodeur.
Animation de la raquette
La raquette est un rectangle, c’est ce qu’il y a de plus simple à dessiner.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
always_comb begin
r = 4'h0; // par défaut
g = 4'h0;
b = 4'h0;
if (inDisplayArea) begin
r = 4'h0; // Couleur de fond
g = 4'h0;
b = 4'h0;
// Dessin de la raquette à la nouvelle position
if (x > paddle_x && x < paddle_x + PADDLE_WIDTH &&
y > paddle_y && y < paddle_y + PADDLE_HEIGHT) begin
r = 4'hF; // Couleur de la raquette
g = 4'h0;
b = 4'h0;
end
end
end
Quand la rotation de l’encodeur est détectée, la position de la raquette est simplement décalée d’une valeur fixe. Et quand la raquette atteint le bord de l’écran, sa position est bloquée.
1
2
3
4
5
6
7
// Mise à jour de la position, verrouille la position si sortie de l'écran
if (dir != 0) begin
if (dir == 1)
paddle_x <= (paddle_x + speed < SCREEN_WIDTH - PADDLE_WIDTH) ? paddle_x + speed : SCREEN_WIDTH - PADDLE_WIDTH;
else
paddle_x <= (paddle_x > speed) ? paddle_x - speed : 0;
end
Le code complet du module drawing.sv :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
module drawing (
input logic clk25,
input logic [9:0] x, y,
input logic signed [1:0] dir,
input logic inDisplayArea,
input logic hsync, vsync,
output logic [3:0] vga_r, vga_g, vga_b,
output logic vga_hsync, vga_vsync
);
localparam PADDLE_WIDTH = 60;
localparam PADDLE_HEIGHT = 20;
localparam SCREEN_WIDTH = 640;
localparam INIT_POSITION = (SCREEN_WIDTH - PADDLE_WIDTH) / 2; // raquette initialement au centre
integer paddle_x = INIT_POSITION;
integer paddle_y = 440;
integer speed = 10; // Réglage de la vitesse
always_ff @(posedge clk25) begin
// Mise à jour de la position, verrouille la position si sortie de l'écran
if (dir != 0) begin
if (dir == 1)
paddle_x <= (paddle_x + speed < SCREEN_WIDTH - PADDLE_WIDTH) ? paddle_x + speed : SCREEN_WIDTH - PADDLE_WIDTH;
else
paddle_x <= (paddle_x > speed) ? paddle_x - speed : 0;
end
end
// ----- Gestion de l'affichage -----
logic [3:0] r, g, b;
always_comb begin
r = 4'h0; // par défaut
g = 4'h0;
b = 4'h0;
if (inDisplayArea) begin
r = 4'h0; // Couleur de fond
g = 4'h0;
b = 4'h0;
// Dessin de la raquette à la nouvelle position
if (x > paddle_x && x < paddle_x + PADDLE_WIDTH &&
y > paddle_y && y < paddle_y + PADDLE_HEIGHT) begin
r = 4'hF; // Couleur de la raquette
g = 4'h0;
b = 4'h0;
end
end
end
always_ff @(posedge clk25) begin
{vga_hsync, vga_vsync} <= {hsync, vsync};
{vga_r, vga_g, vga_b} <= {r, g, b};
end
endmodule
Vous trouverez le projet réalisé avec Intel Quartus Prime sur mon dépôt : FPGA-paddle-VGA.
Forcément, il y aura une suite…