"Pong" The Game, Implemented On An Altera Flex EPF10K70CR240-4 FPGA
"Pong" The Game, Implemented On An Altera Flex EPF10K70CR240-4 FPGA
"Pong" The Game, Implemented On An Altera Flex EPF10K70CR240-4 FPGA
EPF10K70CR240-4 FPGA
Imre Knausz
EE650 @ RIT
Dr. Eric Peskin
November 12, 2004
Introduction..................................................................................................................................... 2
Top Level ........................................................................................................................................ 2
Sub Blocks ...................................................................................................................................... 3
kb_main....................................................................................................................................... 3
keyboard...................................................................................................................................... 3
kb_control ................................................................................................................................... 3
Test.......................................................................................................................................... 4
VGAsync..................................................................................................................................... 4
pongmain..................................................................................................................................... 4
Test.......................................................................................................................................... 5
ball............................................................................................................................................... 5
Test.......................................................................................................................................... 5
paddle.......................................................................................................................................... 5
Test.......................................................................................................................................... 5
paddle2........................................................................................................................................ 5
Test.......................................................................................................................................... 6
Bug(s).............................................................................................................................................. 6
VHDL Code .................................................................................................................................... 6
kb_main....................................................................................................................................... 6
Keyboard..................................................................................................................................... 7
kb_control ................................................................................................................................... 8
VGAsync................................................................................................................................... 10
pongmain................................................................................................................................... 11
ball............................................................................................................................................. 14
paddle........................................................................................................................................ 15
paddle2...................................................................................................................................... 17
Introduction
This project consists of creating an entirely synthesizable “pong” core that can be
implemented on an Altera Flex EPF10k70cr240-4 FPGA. The game uses a VGA monitor as
output and a PS/2 keyboard as input. The game is one player only, and that player plays against
the core’s artificial intelligence. The object of the game is to keep the ball in play by using the
“paddle” to keep the ball in play. The ball is out of bounds when it reaches either the left or right
side of the screen.
It is possible to beat the computer because the ball actually moves faster that the paddles.
When the player wins, the screen turns green and when the FPGA wins, the screen turns red.
Top Level
The top level of this project is a GDF file. It ties together the keyboard controller, game core and
the VGA controller. Below is a screen capture of the GDF:
This game was designed using a hierarchical approach which has several advantages. The game
is easier to code when it is broken apart into fundamental functions, it is easier to understand and
it is easier to debug. The chart below shows the hierarchical structure of the pong core:
Sub Blocks
kb_main
There is no RTL in this block, it simply ties together the keyboard and kb_control blocks.
keyboard
This block interfaces directly with the data and clock pins on the PS/2 keyboard. This code
was supplied by the UP2 manual.
kb_control
This is the keyboard controller block. It controls the keyboard block and reads the
keyboard codes. It interprets keyboard code sequences and decides whether certain keys are
depressed or not. It can detect four keys – up, down, left and right. Below is an ASM chart
describing it’s operation:
The status flag can have three values: E0 code, F0 code and key code. E0 and F0 codes
designate a make or break and the key code designates which key was hit. A more efficient
implementation might have been to use two separate state machines – one to interface with the
keyboard block and another to keep track of scancodes.
Test
This block was tested visually. It was hooked up in a GDF test bench where it’s four outputs
were tied to LEDs. The moment a key (up, down, left or right) was depressed, the associated
LED would turn on and as soon as the key was let go, the LED would turn off.
The ideal way to test the block (and the other blocks for that matter) would have been to
create a test bench and use some ASSERT statements to check whether things were working.
Unfortunately, the Altera software does not support this essential feature of VHDL.
VGAsync
This block interfaces directly with the red, green, blue, hsync and vsync signals on the UP2
board. It generates all the timings required by a VGA monitor. This code was supplied by the
UP2 manual.
pongmain
This is the main module for the pong core. It ties together the ball, paddle and paddle2 state
machines. The state machine contained in this module controls the game start and end. Below is
an ASM chart describing it’s operation:
Test
This block was tested using a simple test bench. Since it does not have many signals (once
the ball, paddle and paddle2 components are removed) it was possible to test it using the
waveform editor in the MaxPlus2 software.
ball
The start of this code came from the ball example in the UP2 manual, however, it was
heavily modified and doesn’t look anything like the original code. The original code had several
problems – the two most significant were the fact that it didn’t compile because the
CONV_STD_LOGIC_VECTOR functions were incorrectly used and it had WAIT statements
which are not supposed to be used in synthesizable code (however, for some reason the Altera
software accepts the use of WAIT).
Ball is a very simple single state state machine, so it’s really not even a machine. It checks
the reset_n signal to reset it’s variables. Otherwise, it moves the ball based on the current
direction speedX and speedY and checks for collisions with the walls and paddles.
Ball (and also the following blocks paddle and paddle2) has a separate PROCESS to “draw”
the ball using RGB signals.
Test
This block was difficult to test. The philosophy for this block, paddle and paddle2 was to
test the individually and incrementally. The first version of the ball block only bounced in the Y
direction. Once that was working, the other dimension was added. Then the paddle block was
created and tested. Finally, I brought the Y position output of the paddle block into the ball
block and wrote the section that detects the paddle collision.
paddle
This code is similar to the ball code, however the paddle moves only in one direction and
based on user input from the keyboard.
Test
This ENTITY was tested by trial and error, since setting up a test bench with waveforms
would have been too difficult. This block was designed and tested incrementally.
paddle2
This code is essentially the same as the paddle block with the exception of an extra
PROCESS which is used to control the paddle. The Track_ball process checks the position and
then moves the paddle according to the following rules: if the ball is “north” of the paddle then
the move_up flag is set and if the ball is “south” of the paddle then the move_down flag is set.
Then in the Move_paddle process, the paddle is moved according to the flags and whether or not
the paddle is already at a screen boundary.
Test
Testing this block was straightforward since it is the same as the paddle block with the
addition of the simple Move_paddle process.
Bug(s)
Unfortunately, the code does have at least one bug that is known. It is in the ball block and
it’s effect is that if you narrowly miss the ball with the paddle, i.e. you are moving after it, the
ball will go behind the paddle, hit the wall and bounce back rather than tell the pongmain state
machine that the wall was hit and the game should be over.
This bug can be fixed by detecting whether the ball is between the paddle’s plane and the
edge of the screen rather that just detecting whether the ball has broken the plane. The ball
seems to be traveling too fast to be detected under some circumstances.
VHDL Code
kb_main
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
ENTITY kb_main IS
PORT(
clk : IN STD_LOGIC;
reset : IN STD_LOGIC;
keyboard_clk, keyboard_data : IN STD_LOGIC;
up, down, left, right : OUT STD_LOGIC);
END kb_main ;
ARCHITECTURE a OF kb_main IS
SIGNAL scancode : STD_LOGIC_VECTOR(7 downto 0);
SIGNAL scan_ready : STD_LOGIC;
SIGNAL read_en : STD_LOGIC;
COMPONENT kb_control
PORT(
clk : IN STD_LOGIC;
scancode : IN STD_LOGIC_VECTOR(7 downto 0);
reset : IN STD_LOGIC;
scan_rdy : IN STD_LOGIC;
read_en : OUT STD_LOGIC;
up, down, left, right : OUT STD_LOGIC);
END COMPONENT kb_control;
COMPONENT keyboard
PORT( keyboard_clk, keyboard_data, clock_25Mhz,
reset, read : IN STD_LOGIC;
scan_code : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
scan_ready : OUT STD_LOGIC);
END COMPONENT keyboard;
BEGIN
kb_control_u1 : kb_control port map
(
clk => clk,
scancode => scancode,
reset => reset,
scan_rdy => scan_ready,
up => up,
down => down,
left => left,
right => right,
read_en => read_en
);
END a;
Keyboard
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.all;
USE IEEE.STD_LOGIC_ARITH.all;
USE IEEE.STD_LOGIC_UNSIGNED.all;
ENTITY keyboard IS
PORT( keyboard_clk, keyboard_data, clock_25Mhz ,
reset, read : IN STD_LOGIC;
scan_code : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
scan_ready : OUT STD_LOGIC);
END keyboard;
ARCHITECTURE a OF keyboard IS
SIGNAL INCNT : std_logic_vector(3 downto 0);
SIGNAL SHIFTIN : std_logic_vector(8 downto 0);
SIGNAL READ_CHAR : std_logic;
SIGNAL INFLAG, ready_set : std_logic;
SIGNAL keyboard_clk_filtered : std_logic;
SIGNAL filter : std_logic_vector(7 downto 0);
BEGIN
--This process filters the raw clock signal coming from the keyboard using a shift
register and two AND gates
Clock_filter: PROCESS
BEGIN
WAIT UNTIL clock_25Mhz'EVENT AND clock_25Mhz= '1';
filter (6 DOWNTO 0) <= filter(7 DOWNTO 1) ;
filter(7) <= keyboard_clk;
IF filter = "11111111" THEN keyboard_clk_filtered <= '1';
ELSIF filter= "00000000" THEN keyboard_clk_filtered <= '0';
END IF;
END PROCESS Clock_filter;
kb_control
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
ENTITY kb_control IS
PORT(
clk : IN STD_LOGIC;
scancode : IN STD_LOGIC_VECTOR(7 downto 0);
reset : IN STD_LOGIC;
scan_rdy : IN STD_LOGIC;
up, down, left, right : OUT STD_LOGIC;
read_en : OUT STD_LOGIC);
END kb_control ;
ARCHITECTURE a OF kb_control IS
TYPE STATE_TYPE IS (idle, readcode);
TYPE STATUS_TYPE IS (None, E0code, F0code);
SIGNAL state : STATE_TYPE;
SIGNAL status : STATUS_TYPE;
BEGIN
PROCESS (clk)
BEGIN
IF reset = '1' THEN
state <= idle;
status <= None;
up <= '0';
down <= '0';
left <= '0';
right <= '0';
ELSIF (clk'EVENT AND clk='1') THEN
CASE state IS
WHEN idle =>
read_en <= '0';
IF (scan_rdy = '0') THEN
state <= idle;
ELSE
state <= readcode;
END IF;
WHEN readcode =>
read_en <= '1';
CASE scancode IS
WHEN "11100000" =>
if ((status=None) OR (status=E0code)) THEN
status <= E0code;
ELSE
status <= None;
END IF;
WHEN "11110000" =>
if ((status=E0code) OR (status=F0code)) THEN
status <= F0code;
ELSE
status <= None;
END IF;
WHEN OTHERS =>
if (status=E0code) THEN
CASE scancode IS
WHEN "01110101" => up <= '1';
WHEN "01110010" => down <= '1';
WHEN "01101011" => left <= '1';
WHEN "01110100" => right <= '1';
WHEN OTHERS => status <= None;
END CASE;
ELSIF (status=F0code) THEN
CASE scancode IS
WHEN "01110101" => up <= '0';
WHEN "01110010" => down <= '0';
WHEN "01101011" => left <= '0';
WHEN "01110100" => right <= '0';
WHEN OTHERS => status <= None;
END CASE;
ELSE
status <= None;
END IF;
END CASE;
WHEN OTHERS =>
state <= idle;
END CASE;
END IF;
END PROCESS;
END a;
VGAsync
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.all;
USE IEEE.STD_LOGIC_ARITH.all;
USE IEEE.STD_LOGIC_UNSIGNED.all;
ENTITY VGA_SYNC IS
END VGA_SYNC;
ARCHITECTURE a OF VGA_SYNC IS
SIGNAL horiz_sync, vert_sync : STD_LOGIC;
SIGNAL video_on, video_on_v, video_on_h : STD_LOGIC;
SIGNAL h_count, v_count : STD_LOGIC_VECTOR(9 DOWNTO 0);
BEGIN
PROCESS
BEGIN
WAIT UNTIL(clock_25Mhz'EVENT) AND (clock_25Mhz='1');
--V_count counts rows of pixels (480 + extra time for sync signals)
--
-- Vert_sync ----------------------------------_______------------
-- V_count 0 480 493-494 524
--
IF (v_count >= 524) AND (h_count >= 699) THEN
v_count <= "0000000000";
ELSIF (h_count = 699) THEN
v_count <= v_count + 1;
END IF;
END PROCESS;
END a;
pongmain
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
ENTITY pongmain IS
PORT(
clk : IN STD_LOGIC;
reset_n : IN STD_LOGIC;
start_n : IN STD_LOGIC;
vert_sync : IN STD_LOGIC;
move_up, move_down : IN STD_LOGIC;
pixel_row, pixel_column : IN STD_LOGIC_VECTOR(9 downto 0);
Red,Green,Blue : OUT STD_LOGIC);
END pongmain ;
ARCHITECTURE a OF pongmain IS
COMPONENT paddle
PORT(
Red,Green,Blue : OUT STD_LOGIC;
Paddle_Y_pos_out : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);
reset_n, vert_sync : IN STD_LOGIC;
move_up, move_down : IN STD_LOGIC;
pixel_row, pixel_column : IN STD_LOGIC_VECTOR(9 downto 0));
END COMPONENT paddle;
COMPONENT paddle2
PORT(
Red,Green,Blue : OUT STD_LOGIC;
Paddle_Y_pos_out : OUT STD_LOGIC_VECTOR(9 DOWNTO 0);
reset_n, vert_sync : IN STD_LOGIC;
ball_Y_pos : IN STD_LOGIC_VECTOR(9 downto 0);
pixel_row, pixel_column : IN STD_LOGIC_VECTOR(9 downto 0));
END COMPONENT paddle2;
BEGIN
PROCESS (clk)
BEGIN
IF reset_n = '0' THEN
state <= S1;
player1win <= '0';
player2win <= '0';
ball_en <= '0';
ELSIF (clk'EVENT AND clk='1') THEN
CASE state IS
WHEN S1 =>
IF (start_n = '0') THEN
state <= S2;
ELSE
state <= S1;
END IF;
WHEN S2 =>
ball_en <= '1';
IF backstop2 = '1' THEN
state <= S3;
player1win <= '1';
ELSIF backstop1 = '1' THEN
state <= S3;
player2win <= '1';
ELSE
state <= S2;
END IF;
WHEN S3 =>
ball_en <= '0';
state <= S3;
WHEN OTHERS =>
state <= S1;
END CASE;
END IF;
END PROCESS;
END a;
ball
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.all;
USE IEEE.STD_LOGIC_ARITH.all;
USE IEEE.STD_LOGIC_UNSIGNED.all;
LIBRARY lpm;
USE lpm.lpm_components.ALL;
ENTITY ball IS
PORT(
SIGNAL Red,Green,Blue : OUT std_logic;
SIGNAL Paddle1_plane, Paddle2_plane : OUT std_logic;
SIGNAL Backstop1, Backstop2 : OUT std_logic;
SIGNAL reset_n, ball_en, vert_sync : IN std_logic;
SIGNAL pixel_row, pixel_column : IN std_logic_vector(9 downto 0);
SIGNAL Paddle_Y_pos, Paddle2_Y_pos : IN std_logic_vector(9 downto 0);
SIGNAL Ball_Y_pos_ext : OUT std_logic_vector(9 downto 0));
END ball;
BEGIN
-- Take care of ball on right side of screen (+8 factor due to ball &
paddle size):
-- Did we hit the wall?
IF '0' & ( Ball_X_pos + CONV_STD_LOGIC_VECTOR(speedX,11) ) >=
CONV_STD_LOGIC_VECTOR(640,10) - Size THEN
Backstop1 <= '1';
-- did we hit paddle1?
ELSIF '0' & ( Ball_X_pos + CONV_STD_LOGIC_VECTOR(speedX+8,11) ) >=
CONV_STD_LOGIC_VECTOR(640,10) - Plane_dist THEN
IF ('0' & Paddle_Y_pos < Ball_Y_pos + Paddle_SizeY) AND (Paddle_Y_pos
+ Paddle_SizeY > '0' & Ball_Y_pos) THEN
i2 <= -speedX;
ELSE
Paddle1_plane <= '1';
END IF;
-- Take care of ball on left side of screen (+8 factor due to ball &
paddle size):
-- Did we hit the wall?
ELSIF '0' & Ball_X_pos <= Size + CONV_STD_LOGIC_VECTOR(speedX,10) THEN
Backstop2 <= '1';
-- did we hit paddle2?
ELSIF Ball_X_pos + CONV_STD_LOGIC_VECTOR(speedX,10) <= Plane_dist +
CONV_STD_LOGIC_VECTOR(8,10) THEN
IF ('0' & Paddle2_Y_pos < Ball_Y_pos + Paddle_SizeY) AND
(Paddle2_Y_pos + Paddle_SizeY > '0' & Ball_Y_pos) THEN
i2 <= speedX;
ELSE
Paddle2_plane <= '1';
END IF;
END IF;
END behavior;
paddle
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.all;
USE IEEE.STD_LOGIC_ARITH.all;
USE IEEE.STD_LOGIC_UNSIGNED.all;
LIBRARY lpm;
USE lpm.lpm_components.ALL;
ENTITY paddle IS
PORT(
SIGNAL Red,Green,Blue : OUT std_logic;
SIGNAL Paddle_Y_pos_out : OUT std_logic_vector(9 DOWNTO 0);
SIGNAL reset_n, vert_sync : IN std_logic;
SIGNAL move_up, move_down : IN std_logic;
signal pixel_row, pixel_column : IN std_logic_vector(9 downto 0));
END paddle;
BEGIN
END behavior;
paddle2
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.all;
USE IEEE.STD_LOGIC_ARITH.all;
USE IEEE.STD_LOGIC_UNSIGNED.all;
LIBRARY lpm;
USE lpm.lpm_components.ALL;
ENTITY paddle2 IS
PORT(
SIGNAL Red,Green,Blue : OUT std_logic;
SIGNAL Paddle_Y_pos_out : OUT std_logic_vector(9 DOWNTO 0);
SIGNAL reset_n, vert_sync : IN std_logic;
signal ball_Y_pos : IN std_logic_vector(9 downto 0);
signal pixel_row, pixel_column : IN std_logic_vector(9 downto 0));
END paddle2;
BEGIN
END behavior;