Les raquettes 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 puis la deuxième, et ce sera déjà bien, si.
Animer la raquette du Pong, 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_pulseproduira 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_syncetdrawing: 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
Et avec deux raquettes
Il suffit de dupliquer le matériel, y compris celui synthétisé dans la puce FPGA :
Et pour animer les deux raquettes, le code du module drawing.sv devient :
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
61
62
63
64
65
66
67
module drawing (
    input logic clk25,
    input logic [9:0] x, y,
    input logic signed [1:0] dir2,
    input logic signed [1:0] dir1,
    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
    localparam X = 0;
    localparam Y = 1;
   
    logic [1:0][1:0] dir; // Tableau contenant 2 bus de 2 bits chacun
    assign dir = '{dir1, dir2};
    logic [9:0] paddle [2][2] = '{ 
                                   '{INIT_POSITION, 440},  // Paddle 1
                                   '{INIT_POSITION,  40}   // Paddle 2
                                 };
   
    integer speed = 10; // vitesse
    always_ff @(posedge clk25) begin 
       for (int i = 0; i < 2; i++) begin
          if (dir[i] != 0) begin
            if (dir[i] == 1) begin
              paddle[i][X] <= (paddle[i][X] + speed < SCREEN_WIDTH - PADDLE_WIDTH) ? paddle[i][X] + speed : SCREEN_WIDTH - PADDLE_WIDTH;
            end else begin
              paddle[i][X] <= (paddle[i][X] > speed) ? paddle[i][X] - speed : 0;
            end
          end
       end
    end
// ----- Gestion de l'affichage -----
    logic [3:0] r, g, b;
    always_comb begin
      r = 4'h0;  // Couleur de fond
      g = 4'hF;
      b = 4'hF; 
      if (inDisplayArea) begin
          for (int i = 0; i < 2; i++) begin
            if (x > paddle[i][X] && x < paddle[i][X] + PADDLE_WIDTH &&
                y > paddle[i][Y] && y < paddle[i][Y] + PADDLE_HEIGHT) begin
                  r = 4'hF;  // Couleur de la raquette
                  g = 4'h0;
                  b = 4'h0;
            end
          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…


