Skocz do zawartości

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


Pomocna odpowiedź

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:

// 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:

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:

  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ść:

`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:


// 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 :

   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

PONG01.zip

PONG02.zip

Link do komentarza
Share on other sites

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

Link do komentarza
Share on other sites

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 ^^

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.