Skocz do zawartości

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


Pomocna odpowiedź

Napisano

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

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

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

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...