Post

La raquette du Pong sur FPGA

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

vue RTL, raquette pong

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ée CLOCK_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 sortie dir[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 module encoder_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_syncet drawing : 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…

Cet article est sous licence CC BY 4.0 par l'auteur.