Ta strona używa ciasteczek (plików cookies), dzięki którym może działać lepiej. Dowiedz się więcejRozumiem i akceptuję

Gra PONG VGA - odbijanie piłeczki (Elbert V2)

Autor Wiadomość
FlyingDutch 




Posty: 160
Pomógł: 1 raz
Otrzymał 7 piw(a)
Skąd: Bydgoszcz
Programuję w:
C++,Python
Wysłany: 06-02-2018, 19:24   Gra PONG VGA - odbijanie piłeczki (Elbert V2)

Cześć,

po przeczytaniu postu Elvisa dot. generowania obrazu na ekranie VGA postanowiłem też zrobić jakąś próbę w tym temacie. Mój wybór padł na najprostszą z możliwych gier PONG.
Korzystałem z materiałów z tej strony WWW:

http://www.fpga4fun.com/PongGame.html

Gra jest napisana w języku Verilog i oryginalnie korzystała z płytki FPGA z zegarem 25 MHz, stąd użyłem generatora pętli synchronizacji fazowej PLL ze środowiska "ISE" (patrz post Elvisa dot. generowania obrazu VGA) do generowania zegara 25 MHz.

Gra korzysta też z modułu do generacji częstotliwości synchronizacji poziomej i pionowej - moduł hvsync_generator (z częstotliwości wej 25 MHz).

W oryginalnej grze sterowanie pozycją paletki było za pomocą myszy podłączonej do układu FPGA.

Oto kod modułu głównego projektu - pong.v:

Kod programu: Zaznacz cały

// Pong VGA game
// (c) fpga4fun.com

module pong(clk, vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B, quadA, quadB);
input clk;
output vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B;
input quadA, quadB;

wire inDisplayArea;
wire [9:0] CounterX;
wire [8:0] CounterY;
wire clkO25MHz;

// Instantiate the module pll
pll zegar (
    .CLKIN_IN(clk),
    .CLKFX_OUT(clkO25MHz),
    .CLKIN_IBUFG_OUT(CLKIN_IBUFG_OUT),
    .CLK0_OUT(CLK0_OUT)
    );

hvsync_generator syncgen(.clk25MHz(clkO25MHz), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync),
  .inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY));

/////////////////////////////////////////////////////////////////
reg [8:0] PaddlePosition;
reg [2:0] quadAr, quadBr;

always @(posedge clkO25MHz) quadAr <= {quadAr[1:0], quadA};
always @(posedge clkO25MHz) quadBr <= {quadBr[1:0], quadB};

always @(posedge clkO25MHz)
if(quadAr[2] ^ quadAr[1] ^ quadBr[2] ^ quadBr[1])
begin
    if(quadAr[2] ^ quadBr[1])
    begin
        if(~&PaddlePosition)        // make sure the value doesn't overflow
            PaddlePosition <= PaddlePosition + 1;
    end
    else
    begin
        if(|PaddlePosition)        // make sure the value doesn't underflow
            PaddlePosition <= PaddlePosition - 1;
    end
end

/////////////////////////////////////////////////////////////////
reg [9:0] ballX;
reg [8:0] ballY;
reg ball_inX, ball_inY;

always @(posedge clkO25MHz)
if(ball_inX==0) ball_inX <= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16);

always @(posedge clkO25MHz)
if(ball_inY==0) ball_inY <= (CounterY==ballY); else ball_inY <= !(CounterY==ballY+16);

wire ball = ball_inX & ball_inY;

/////////////////////////////////////////////////////////////////
wire border = (CounterX[9:3]==0) || (CounterX[9:3]==74) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27);
wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself

reg ResetCollision;
always @(posedge clkO25MHz) ResetCollision <= (CounterY==500) & (CounterX==0);  // active only once for every video frame

reg CollisionX1, CollisionX2, CollisionY1, CollisionY2;
always @(posedge clkO25MHz) if(ResetCollision) CollisionX1<=0; else if(BouncingObject & (CounterX==ballX   ) & (CounterY==ballY+ 8)) CollisionX1<=1;
always @(posedge clkO25MHz) if(ResetCollision) CollisionX2<=0; else if(BouncingObject & (CounterX==ballX+16) & (CounterY==ballY+ 8)) CollisionX2<=1;
always @(posedge clkO25MHz) if(ResetCollision) CollisionY1<=0; else if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY   )) CollisionY1<=1;
always @(posedge clkO25MHz) if(ResetCollision) CollisionY2<=0; else if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY+16)) CollisionY2<=1;

/////////////////////////////////////////////////////////////////
wire UpdateBallPosition = ResetCollision;  // update the ball position at the same time that we reset the collision detectors

reg ball_dirX, ball_dirY;
always @(posedge clkO25MHz)
if(UpdateBallPosition)
begin
    if(~(CollisionX1 & CollisionX2))        // if collision on both X-sides, don't move in the X direction
    begin
        ballX <= ballX + (ball_dirX ? -1 : 1);
        if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0;
    end

    if(~(CollisionY1 & CollisionY2))        // if collision on both Y-sides, don't move in the Y direction
    begin
        ballY <= ballY + (ball_dirY ? -1 : 1);
        if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
    end
end

/////////////////////////////////////////////////////////////////
wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]);
wire G = BouncingObject | ball;
wire B = BouncingObject | ball;

reg vga_R, vga_G, vga_B;
always @(posedge clkO25MHz)
begin
    vga_R <= R & inDisplayArea;
    vga_G <= G & inDisplayArea;
    vga_B <= B & inDisplayArea;
end

endmodule


Tutaj moduł generatora synchronizacji - hvsync_generator.v:

Kod programu: Zaznacz cały

module hvsync_generator(clk25MHz, vga_h_sync, vga_v_sync, inDisplayArea, CounterX, CounterY);
input clk25MHz;
output vga_h_sync, vga_v_sync;
output inDisplayArea;
output [9:0] CounterX;
output [8:0] CounterY;

//////////////////////////////////////////////////
reg [9:0] CounterX;
reg [8:0] CounterY;
wire CounterXmaxed = (CounterX==10'h2FF); //10'h2FF

always @(posedge clk25MHz)
if(CounterXmaxed)
    CounterX <= 0;
else
    CounterX <= CounterX + 1;

always @(posedge clk25MHz)
if(CounterXmaxed) CounterY <= CounterY + 1;

reg    vga_HS, vga_VS;
always @(posedge clk25MHz)
begin
    vga_HS <= (CounterX[9:4]==6'h2D); // change this value to move the display horizontally
    vga_VS <= (CounterY==500); // change this value to move the display vertically
end

reg inDisplayArea;
always @(posedge clk25MHz)
if(inDisplayArea==0)
    inDisplayArea <= (CounterXmaxed) && (CounterY<480);
else
    inDisplayArea <= !(CounterX==639); //CounterX==639
   
assign vga_h_sync = ~vga_HS;
assign vga_v_sync = ~vga_VS;

endmodule


A tutaj kod z mapowaniem pinów (user constraint) dla Elbert V2- pong.ucf:

Kod programu: Zaznacz cały

   NET "clk"           LOC = P129  | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz;
    NET "vga_h_sync"    LOC = P93   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_v_sync"    LOC = P92   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_B"         LOC = P98   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
#   NET "blue[1]"       LOC = P96   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_G"         LOC = P102  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
#   NET "green[1]"      LOC = P101  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_R"         LOC = P105  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;


Obraz u mnie na monitorze VGA rysuje się poprawnie - piłeczka odbija się od krawędzi ekranu i paletki.

Trzeba teraz tylko zrobić przemieszczanie paletki za pomocą dwóch switch'y z płytki Elbert (zamiast kodu do myszki w projekcie). Chyba wezmę się za to jutro (dzisiaj muszę jeszcze coś dokończyć) i jest szansa, że mnie ktoś w tym ubiegnie ;-)

W załączniku spakowany cały projekt ISE gry PONG.

Pozdrawiam

[ Dodano: 07-02-2018, 19:00 ]
Cześć,

miałem dzisiaj trochę czasu, aby dokończyć grę PONG. Przesuwanie paletki za pomocą dwóch switch buttonów (Elbert: SW1 i SW2 górne) działa jak należy.

Co trzeba zrobić:

1) Dodajemy do projektu ISE plik slowClock.v Oto jego treść:

Kod programu: Zaznacz cały

`timescale 1ns / 1ps

module slowClock(clk, reset, clk_1Hz);
input clk, reset;
output clk_1Hz;

reg clk_1Hz = 1'b0;
reg [17:0] counter;

always@(negedge reset or posedge clk)
begin
    if (reset == 1'b0)
        begin
            clk_1Hz <= 0;
            counter <= 0;
        end
    else
        begin
            counter <= counter + 1;
            if ( counter == 97_656)
                begin
                    counter <= 0;
                    clk_1Hz <= ~clk_1Hz; //okolo 256 Hz
                end
        end
end
endmodule   


Modyfikujemy moduł główny - pong.v:

Kod programu: Zaznacz cały


// Pong VGA game
// (c) fpga4fun.com

module pong(clk, rst, vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B, btLeft, btRigth);
input clk, rst;
output vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B;
input btLeft, btRigth;

wire inDisplayArea;
wire [9:0] CounterX;
wire [8:0] CounterY;
wire clkO25MHz;
wire clkO1Hz; //okolo 256 Hz

// Instantiate the module pll
pll zegar (
    .CLKIN_IN(clk),
    .CLKFX_OUT(clkO25MHz),
    .CLKIN_IBUFG_OUT(CLKIN_IBUFG_OUT),
    .CLK0_OUT(CLK0_OUT)
    );

hvsync_generator syncgen(.clk25MHz(clkO25MHz), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync),
  .inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY));
 
slowClock zegar1Hz (.clk(clkO25MHz),
          .reset(rst),
          .clk_1Hz(clkO1Hz)
 );

/////////////////////////////////////////////////////////////////
reg [8:0] PaddlePosition;

always @(negedge clkO1Hz)
if ((btLeft == 1'b0) || (btRigth == 1'b0))
begin
    if (btRigth == 1'b0)
    begin
        if(~&PaddlePosition)        // make sure the value doesn't overflow
            PaddlePosition <= PaddlePosition + 1;
    end
    else if (btLeft == 1'b0)
    begin
        if(|PaddlePosition)        // make sure the value doesn't underflow
            PaddlePosition <= PaddlePosition - 1;
    end
end

/////////////////////////////////////////////////////////////////
reg [9:0] ballX;
reg [8:0] ballY;
reg ball_inX, ball_inY;

always @(posedge clkO25MHz)
if(ball_inX==0) ball_inX <= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16);

always @(posedge clkO25MHz)
if(ball_inY==0) ball_inY <= (CounterY==ballY); else ball_inY <= !(CounterY==ballY+16);

wire ball = ball_inX & ball_inY;

/////////////////////////////////////////////////////////////////
wire border = (CounterX[9:3]==0) || (CounterX[9:3]==74) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27);
wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself

reg ResetCollision;
always @(posedge clkO25MHz) ResetCollision <= (CounterY==500) & (CounterX==0);  // active only once for every video frame

reg CollisionX1, CollisionX2, CollisionY1, CollisionY2;
always @(posedge clkO25MHz) if(ResetCollision) CollisionX1<=0; else if(BouncingObject & (CounterX==ballX   ) & (CounterY==ballY+ 8)) CollisionX1<=1;
always @(posedge clkO25MHz) if(ResetCollision) CollisionX2<=0; else if(BouncingObject & (CounterX==ballX+16) & (CounterY==ballY+ 8)) CollisionX2<=1;
always @(posedge clkO25MHz) if(ResetCollision) CollisionY1<=0; else if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY   )) CollisionY1<=1;
always @(posedge clkO25MHz) if(ResetCollision) CollisionY2<=0; else if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY+16)) CollisionY2<=1;

/////////////////////////////////////////////////////////////////
wire UpdateBallPosition = ResetCollision;  // update the ball position at the same time that we reset the collision detectors

reg ball_dirX, ball_dirY;
always @(posedge clkO25MHz)
if(UpdateBallPosition)
begin
    if(~(CollisionX1 & CollisionX2))        // if collision on both X-sides, don't move in the X direction
    begin
        ballX <= ballX + (ball_dirX ? -1 : 1);
        if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0;
    end

    if(~(CollisionY1 & CollisionY2))        // if collision on both Y-sides, don't move in the Y direction
    begin
        ballY <= ballY + (ball_dirY ? -1 : 1);
        if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
    end
end

/////////////////////////////////////////////////////////////////
wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]);
wire G = BouncingObject | ball;
wire B = BouncingObject | ball;

reg vga_R, vga_G, vga_B;
always @(posedge clkO25MHz)
begin
    vga_R <= R & inDisplayArea;
    vga_G <= G & inDisplayArea;
    vga_B <= B & inDisplayArea;
end

endmodule


Zmieniamy plik - pong.ucf :

Kod programu: Zaznacz cały

    NET "clk"           LOC = P129  | IOSTANDARD = LVCMOS33 | PERIOD = 12MHz;
    NET "vga_h_sync"    LOC = P93   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_v_sync"    LOC = P92   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_B"         LOC = P98   | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_G"         LOC = P102  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
    NET "vga_R"         LOC = P105  | IOSTANDARD = LVCMOS33 | SLEW = SLOW | DRIVE = 12;
       
NET "rst" PULLUP;
NET "rst"  LOC = P76;

NET "btLeft" PULLUP;
NET "btLeft"  LOC = P80;

NET "btRigth" PULLUP;
NET "btRigth"  LOC = P79;


I to wszystko. W dalszej perspektywie trzeba by było:
1) Przyśpieszyć ruch piłeczki
2) Dodać licznik punktów

Pamiętam, że oryginalny PONG, który pojawił się w "obwoźnych" budach w Polsce pod koniec lat 70-tych XX wieku, miał też takie "ficzery" jak:
1) Im dłużej się grało tym paletka robiła się krótsza, a piłeczka poruszała się coraz szybciej

W drugim załączniku spakowany projekt ISE gry (poprawiony).

Pozdrawiam

Polecany artykuł » Kurs STM32 F4 - #12 - Programowanie przez bootloader




PONG02.zip
Projekt poprawiony gry PONG - FPGA Elbert V2
Pobierz Plik ściągnięto 4 raz(y) 379.51 KB

PONG01.zip
Projekt ISE gry PONG - FPGA Elbert V2
Pobierz Plik ściągnięto 3 raz(y) 345.68 KB


Anioły to też demony - lepiej ich nie wkurzaj
Ostatnio zmieniony przez FlyingDutch 07-02-2018, 19:11, w całości zmieniany 7 razy  
Postaw piwo autorowi tego posta
 
 
Elvis 



Posty: 1557
Pomógł: 98 razy
Otrzymał 269 piw(a)
Skąd: wawa
Programuję w:
C, asm
Wysłany: 07-02-2018, 20:46   

Postanowiłeś jednak używać Veriloga?

Postaw piwo autorowi tego posta
 
 
FlyingDutch 




Posty: 160
Pomógł: 1 raz
Otrzymał 7 piw(a)
Skąd: Bydgoszcz
Programuję w:
C++,Python
Wysłany: 08-02-2018, 08:04   

Elvis napisał/a:
Postanowiłeś jednak używać Veriloga?


Cześć Elvis,

raczej nigdy nie postanowiłem, że nie będę używał Veriloga ;-)

Mówiłem, że w swoich projektach pisanych w VHDL wolałbym mieć tylko jeden język (VHDL).
Nie oznacza to, abym postanowił nie używać Veriloga w ogóle, szczególnie jak jest jakiś fajny projekt w tym języku.

Pozdrawiam


Anioły to też demony - lepiej ich nie wkurzaj
Postaw piwo autorowi tego posta
 
 
JTyburski 



Posty: 51
Pomógł: 3 razy
Otrzymał 5 piw(a)
Skąd: Warszawa
Programuję w:
VHDL, Verilog
Wysłany: 13-02-2018, 10:39   

Pong to bym powiedział klasyka na rozgrzewkę. Niemniej skoro to potrafisz to zrób teraz naszą rodzimą polską konsolę "Ameprod tvg-10" (z przełomu lat 70 i 80-tych) na FPGA ^^ (zaemuluj, zrób od zera, tylko nie na zasadzie zdobywania oryginalnego scalaka i sterowania nim, tylko funkcjonalność, żeby była cała na FPGA) ^^ Ale to tak jako dalsza praktyka ^^

Ostatnio popularny » Kurs elektroniki - #10 - Podsumowanie, Quiz


Postaw piwo autorowi tego posta
 
 
Wyświetl posty z ostatnich:   
Odpowiedz do tematu
Nie możesz pisać nowych tematów
Nie możesz odpowiadać w tematach
Nie możesz zmieniać swoich postów
Nie możesz usuwać swoich postów
Nie możesz głosować w ankietach
Nie możesz załączać plików na tym forum
Możesz ściągać załączniki na tym forum
Wersja do druku

Skocz do:  

Nie rozwiązałeś swojego problemu? Zobacz podobne tematy: [FPGA] Sterowanie se... Realizacja pamięci p... Sposoby dołączenia u... Jak się projektuje u...
lub przeszukaj forum po wybranych tagach: elbert, gra, odbijanie, pileczki, pong, v2, vga


Powered by phpBB modified by Przemo © 2003 phpBB Group
Popularne kursy: Arduinopodstawy elektroniki