PONG game in VHDL and Verilog on the Go Board
Recreate the high-tech of the 70’s
Welcome to the last Go Board tutorial. It’s been a long climb, but this project represents the culmination of all of your hard work. And what better way to end everything than by recreating PONG? I assume you know how Pong works. Two paddles on either side of the screen, a ball in the middle, and all the madness that black and white graphics has to offer. You try to hit the ball with your paddle to keep it bouncing back toward your opponent. If you miss the ball, you opponent scores, if your opponent misses, you score.
Should you prefer to follow along with a YouTube video, well you can do that too! Please subscribe to my channel.
This project is going to use the push-buttons on the Go Board to control the paddles. Player 1 is going to use Switches 1 and 2, Player 2 is going to use Switches 3 and 4. This does require getting your fingers pretty close together, so play against someone you don’t mind brushing fingers with. Let’s jump right in and look at the Block Diagram of the code to show how this project is going to be organized. I’ll go through each of these blocks individually and talk about them briefly. Note that all of the blocks in GREEN are reused verbatim from previous Go Board Projects. Neat how all of this code builds on itself huh? It’s almost like I planned all of this.
Overview of Pong Game:
The way I decided to implement this game is by creating little functional blocks for each of the paddles and the ball itself. The Pong Board I set to be 40×30 pixels. I chose these because 40×30 is equal to 640×480 divided by 16 (640/16=40, 480/16=30). One thing about FPGAs, it’s really hard to do division inside of an FPGA unless you are dividing by a power of 2. When dividing by a power of 2, simply drop the number of least significant bits that are required to represent the divisor. For our example, we are dividing by 16, which takes 4 bits to represent. So if we drop the least significant 4 bits off of the Row and Column indexes then we have divided them by 16.
Base-2 multiplying and dividing is done constantly inside of an FPGA, so it’s important to understand. If you want to multiply a number by 2, simply add on a single bit on the right of the number. To multiply by 8, add on 3 bits (set to zero). Remove those bits to divide.
Each of the Paddle and Ball modules keeps track of their single component based on the current Row/Col index of the frame. If the current pixel is equal to the location of the paddle or the ball, those modules will drive their output high. This tells the Pong Top to draw white on that pixel. See below for a complete description of each module.
UART RX:
There needs to be a way to kick-off a new Pong Game, to tell the Go Board to start moving the ball. It would have been easier if there were five push-button switches on the Go Board, so that one of them could be use to start the game, but sadly there isn’t. You could have chosen to use one of the other buttons to start a new game, but I just decided to reuse the UART RX module from a previous project. Any time the UART RX asserts its Data Valid (DV) pulse, then the game starts.
Sync Pulse Generator, Sync To Count, VGA Sync Porch:
All three of these modules came directly from the previous project where we drove Test Patterns to a VGA monitor from the Go Board. To learn how each of those work go back and review that project, they are used as-is here.
Debounce Switches:
One thing’s for sure, we can’t use the raw switch inputs. We need to debounce those buttons. This code was already written in a previous project, so just lift that and instantiate it once for each of the four switch inputs.
P1/P2 Paddle Control:
Here’s the actual first new piece of code thus far. The Paddle Control logic is exactly the same for Player 1 and Player 2, the only way to know which is which is by setting a Generic in VHDL or a Parameter in Verilog. This module takes in the Current Row/Col that we are drawing on the screen. It knows where the paddle is located and keeps track of that if the player moves their paddle up or down. If the current active row/col index is equal to the location of the paddle, it will draw the paddle on the screen. The output o_Draw_Paddle when 1 will draw a pixel at the current active row/col location.
Ball Control:
This module keeps track of the ball location in Row/Col. The row/col that is being drawn on the VGA display is sent as an input to this module. If the current active pixel is the same pixel as where the ball is located, the output o_Draw_Ball becomes a 1. Otherwise it’s a 0.
Big Or Gate:
This isn’t its own module. The purpose of this Or Gate is to look at the outputs of P1 Paddle Control, P2 Paddle Control, and Ball Control. If any of them are telling the VGA display to draw at this Row/Col index, then it will allow the output to go high.
That’s it! Below is the code for each of these new modules
Your Future Improvements
There are a few things that you can change about Pong to make it better. Here are a few ideas:
- Keep Score Using the 7-Segment Displays (P1=Left, P2=Right)
- Add Ability to Change Colors of Pong Game, Paddles, Ball
- Add UART Control of Dynamic Color Selections
- Anything Else?
Go Board Conclusion
I sincerely hope that you have found your development using the Go Board to be fun and worthwhile. I have spend thousands of hours creating this website, designing the Go Board, and writing all of these tutorials. Please post to social media and tell others about this place, that’s the best way to help! If you have any feedback, either email me using the link on the bottom of this webpage or post to the comment section. And thank you so much for your support, I could not have done all of this without you and your positive encouragement!
Project10_Pong_Top.vhd:
------------------------------------------------------------------------------- -- File Downloaded From http://www.nandland.com ------------------------------------------------------------------------------- library ieee; use ieee.std_logic_1164.all; entity Project10_Pong_Top is port ( -- Main Clock (25 MHz) i_Clk : in std_logic; -- UART Data i_UART_RX : in std_logic; -- Push Buttons i_Switch_1 : in std_logic; i_Switch_2 : in std_logic; i_Switch_3 : in std_logic; i_Switch_4 : in std_logic; -- VGA o_VGA_HSync : out std_logic; o_VGA_VSync : out std_logic; o_VGA_Red_0 : out std_logic; o_VGA_Red_1 : out std_logic; o_VGA_Red_2 : out std_logic; o_VGA_Grn_0 : out std_logic; o_VGA_Grn_1 : out std_logic; o_VGA_Grn_2 : out std_logic; o_VGA_Blu_0 : out std_logic; o_VGA_Blu_1 : out std_logic; o_VGA_Blu_2 : out std_logic ); end entity Project10_Pong_Top; architecture RTL of Project10_Pong_Top is signal w_RX_DV : std_logic; -- VGA Constants to set Frame Size constant c_VIDEO_WIDTH : integer := 3; constant c_TOTAL_COLS : integer := 800; constant c_TOTAL_ROWS : integer := 525; constant c_ACTIVE_COLS : integer := 640; constant c_ACTIVE_ROWS : integer := 480; -- Common VGA Signals signal w_HSync_VGA : std_logic; signal w_VSync_VGA : std_logic; signal w_Red_Video_Porch : std_logic_vector(c_VIDEO_WIDTH-1 downto 0); signal w_Grn_Video_Porch : std_logic_vector(c_VIDEO_WIDTH-1 downto 0); signal w_Blu_Video_Porch : std_logic_vector(c_VIDEO_WIDTH-1 downto 0); signal w_Switch_1 : std_logic; signal w_Switch_2 : std_logic; signal w_Switch_3 : std_logic; signal w_Switch_4 : std_logic; -- Pong Signals signal w_HSync_Pong : std_logic; signal w_VSync_Pong : std_logic; signal w_Red_Video_Pong : std_logic_vector(c_VIDEO_WIDTH-1 downto 0); signal w_Grn_Video_Pong : std_logic_vector(c_VIDEO_WIDTH-1 downto 0); signal w_Blu_Video_Pong : std_logic_vector(c_VIDEO_WIDTH-1 downto 0); begin UART_RX_Inst : entity work.UART_RX generic map ( g_CLKS_PER_BIT => 217) -- 25,000,000 / 115,200 port map ( i_Clk => i_Clk, i_RX_Serial => i_UART_RX, o_RX_DV => w_RX_DV, o_RX_Byte => open); VGA_Sync_Pulses_inst : entity work.VGA_Sync_Pulses generic map ( g_TOTAL_COLS => c_TOTAL_COLS, g_TOTAL_ROWS => c_TOTAL_ROWS, g_ACTIVE_COLS => c_ACTIVE_COLS, g_ACTIVE_ROWS => c_ACTIVE_ROWS ) port map ( i_Clk => i_Clk, o_HSync => w_HSync_VGA, o_VSync => w_VSync_VGA, o_Col_Count => open, o_Row_Count => open ); Debounce_Switch_1: entity work.Debounce_Switch port map ( i_Clk => i_Clk, i_Switch => i_Switch_1, o_Switch => w_Switch_1); Debounce_Switch_2: entity work.Debounce_Switch port map ( i_Clk => i_Clk, i_Switch => i_Switch_2, o_Switch => w_Switch_2); Debounce_Switch_3: entity work.Debounce_Switch port map ( i_Clk => i_Clk, i_Switch => i_Switch_3, o_Switch => w_Switch_3); Debounce_Switch_4: entity work.Debounce_Switch port map ( i_Clk => i_Clk, i_Switch => i_Switch_4, o_Switch => w_Switch_4); Pong_Top_1: entity work.Pong_Top generic map ( g_Video_Width => c_VIDEO_WIDTH, g_Total_Cols => c_TOTAL_COLS, g_Total_Rows => c_TOTAL_ROWS, g_Active_Cols => c_ACTIVE_COLS, g_Active_Rows => c_ACTIVE_ROWS) port map ( i_Clk => i_Clk, i_HSync => w_HSync_VGA, i_VSync => w_VSync_VGA, i_Game_Start => w_RX_DV, i_Paddle_Up_P1 => w_Switch_1, i_Paddle_Dn_P1 => w_Switch_2, i_Paddle_Up_P2 => w_Switch_3, i_Paddle_Dn_P2 => w_Switch_4, o_HSync => w_HSync_Pong, o_VSync => w_VSync_Pong, o_Red_Video => w_Red_Video_Pong, o_Blu_Video => w_Blu_Video_Pong, o_Grn_Video => w_Grn_Video_Pong); VGA_Sync_Porch_Inst : entity work.VGA_Sync_Porch generic map ( g_Video_Width => c_VIDEO_WIDTH, g_TOTAL_COLS => c_TOTAL_COLS, g_TOTAL_ROWS => c_TOTAL_ROWS, g_ACTIVE_COLS => c_ACTIVE_COLS, g_ACTIVE_ROWS => c_ACTIVE_ROWS ) port map ( i_Clk => i_Clk, i_HSync => w_HSync_Pong, i_VSync => w_VSync_Pong, i_Red_Video => w_Red_Video_Pong, i_Grn_Video => w_Blu_Video_Pong, i_Blu_Video => w_Grn_Video_Pong, -- o_HSync => o_VGA_HSync, o_VSync => o_VGA_VSync, o_Red_Video => w_Red_Video_Porch, o_Grn_Video => w_Blu_Video_Porch, o_Blu_Video => w_Grn_Video_Porch ); o_VGA_Red_0 <= w_Red_Video_Porch(0); o_VGA_Red_1 <= w_Red_Video_Porch(1); o_VGA_Red_2 <= w_Red_Video_Porch(2); o_VGA_Grn_0 <= w_Grn_Video_Porch(0); o_VGA_Grn_1 <= w_Grn_Video_Porch(1); o_VGA_Grn_2 <= w_Grn_Video_Porch(2); o_VGA_Blu_0 <= w_Blu_Video_Porch(0); o_VGA_Blu_1 <= w_Blu_Video_Porch(1); o_VGA_Blu_2 <= w_Blu_Video_Porch(2); end architecture RTL;
Pong_Top.vhd:
-- This module is designed for 640x480 with a 25 MHz input clock. library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library work; use work.Pong_Pkg.all; entity Pong_Top is generic ( g_Video_Width : integer; g_Total_Cols : integer; g_Total_Rows : integer; g_Active_Cols : integer; g_Active_Rows : integer ); port ( i_Clk : in std_logic; i_HSync : in std_logic; i_VSync : in std_logic; -- Game Start Button i_Game_Start : in std_logic; -- Player 1 & Player 2 Controls (Controls Paddles) i_Paddle_Up_P1 : in std_logic; i_Paddle_Dn_P1 : in std_logic; i_Paddle_Up_P2 : in std_logic; i_Paddle_Dn_P2 : in std_logic; o_HSync : out std_logic; o_VSync : out std_logic; o_Red_Video : out std_logic_vector(g_Video_Width-1 downto 0); o_Blu_Video : out std_logic_vector(g_Video_Width-1 downto 0); o_Grn_Video : out std_logic_vector(g_Video_Width-1 downto 0) ); end entity Pong_Top; architecture rtl of Pong_Top is type t_SM_Main is (s_Idle, s_Running, s_P1_Wins, s_P2_Wins, s_Cleanup); signal r_SM_Main : t_SM_Main := s_Idle; signal w_HSync : std_logic; signal w_VSync : std_logic; -- Make these unsigned counters (always positive) signal w_Col_Count : std_logic_vector(9 downto 0); signal w_Row_Count : std_logic_vector(9 downto 0); -- Divided version of the Row/Col Counters. -- Allows us to make the board 40x30 signal w_Col_Count_Div : std_logic_vector(5 downto 0) := (others => '0'); signal w_Row_Count_Div : std_logic_vector(5 downto 0) := (others => '0'); -- Integer representation of the above counters. -- Integers are easier to work with conceptually signal w_Col_Index : integer range 0 to 2**w_Col_Count_Div'length-1 := 0; signal w_Row_Index : integer range 0 to 2**w_Row_Count_Div'length-1 := 0; signal w_Draw_Paddle_P1 : std_logic; signal w_Draw_Paddle_P2 : std_logic; signal w_Paddle_Y_P1 : std_logic_vector(5 downto 0); signal w_Paddle_Y_P2 : std_logic_vector(5 downto 0); signal w_Draw_Ball : std_logic; signal w_Ball_X : std_logic_vector(5 downto 0); signal w_Ball_Y : std_logic_vector(5 downto 0); signal w_Draw_Any : std_logic; signal w_Game_Active : std_logic; signal w_Paddle_Y_P1_Top : unsigned(5 downto 0); signal w_Paddle_Y_P1_Bot : unsigned(5 downto 0); signal w_Paddle_Y_P2_Top : unsigned(5 downto 0); signal w_Paddle_Y_P2_Bot : unsigned(5 downto 0); signal r_P1_Score : integer range 0 to c_Score_Limit := 0; signal r_P2_Score : integer range 0 to c_Score_Limit := 0; begin Sync_To_Count_inst : entity work.Sync_To_Count generic map ( g_Total_Cols => g_Total_Cols, g_Total_Rows => g_Total_Rows ) port map ( i_Clk => i_Clk, i_HSync => i_HSync, i_VSync => i_VSync, o_HSync => w_HSync, o_VSync => w_VSync, o_Col_Count => w_Col_Count, o_Row_Count => w_Row_Count ); -- Register syncs to align with output data. p_Reg_Syncs : process (i_Clk) is begin if rising_edge(i_Clk) then o_VSync <= w_VSync; o_HSync <= w_HSync; end if; end process p_Reg_Syncs; -- Drop 4 LSBs, which effectively divides by 16 w_Col_Count_Div <= w_Col_Count(w_Col_Count'left downto 4); w_Row_Count_Div <= w_Row_Count(w_Row_Count'left downto 4); -- Instantiation of Paddle Control + Draw for Player 1 Paddle_Ctrl_P1_inst : Pong_Paddle_Ctrl generic map ( g_Player_Paddle_X => c_Paddle_Col_Location_P1 ) port map ( i_Clk => i_Clk, i_Col_Count_Div => w_Col_Count_Div, i_Row_Count_Div => w_Row_Count_Div, i_Paddle_Up => i_Paddle_Up_P1, i_Paddle_Dn => i_Paddle_Dn_P1, o_Draw_Paddle => w_Draw_Paddle_P1, o_Paddle_Y => w_Paddle_Y_P1 ); -- Instantiation of Paddle Control + Draw for Player 2 Paddle_Ctrl_P2_inst : Pong_Paddle_Ctrl generic map ( g_Player_Paddle_X => c_Paddle_Col_Location_P2 ) port map ( i_Clk => i_Clk, i_Col_Count_Div => w_Col_Count_Div, i_Row_Count_Div => w_Row_Count_Div, i_Paddle_Up => i_Paddle_Up_P2, i_Paddle_Dn => i_Paddle_Dn_P2, o_Draw_Paddle => w_Draw_Paddle_P2, o_Paddle_Y => w_Paddle_Y_P2 ); -- Instantiation of Ball Control + Draw Pong_Ball_Ctrl_inst : Pong_Ball_Ctrl port map ( i_Clk => i_Clk, i_Game_Active => w_Game_Active, i_Col_Count_Div => w_Col_Count_Div, i_Row_Count_Div => w_Row_Count_Div, o_Draw_Ball => w_Draw_Ball, o_Ball_X => w_Ball_X, o_Ball_Y => w_Ball_Y ); -- Create Intermediary signals for P1 and P2 Paddle Top and Bottom positions w_Paddle_Y_P1_Bot <= unsigned(w_Paddle_Y_P1); w_Paddle_Y_P1_Top <= w_Paddle_Y_P1_Bot + to_unsigned(c_Paddle_Height, w_Paddle_Y_P1_Bot'length); w_Paddle_Y_P2_Bot <= unsigned(w_Paddle_Y_P2); w_Paddle_Y_P2_Top <= w_Paddle_Y_P2_Bot + to_unsigned(c_Paddle_Height, w_Paddle_Y_P2_Bot'length); -- Create a state machine to control the state of play p_SM_Main : process (i_Clk) is begin if rising_edge(i_Clk) then case r_SM_Main is -- Stay in this state until Game Start button is hit when s_Idle => if i_Game_Start = '1' then r_SM_Main <= s_Running; end if; -- Stay in this state until either player misses the ball -- Can only occur when the Ball is at 0 or c_Game_Width-1 when s_Running => -- Player 1's Side: if w_Ball_X = std_logic_vector(to_unsigned(0, w_Ball_X'length)) then if (unsigned(w_Ball_Y) < w_Paddle_Y_P1_Bot or unsigned(w_Ball_Y) > w_Paddle_Y_P1_Top) then r_SM_Main <= s_P2_Wins; end if; -- Player 2's Side: elsif w_Ball_X = std_logic_vector(to_unsigned(c_Game_Width-1, w_Ball_X'length)) then if (unsigned(w_Ball_Y) < w_Paddle_Y_P2_Bot or unsigned(w_Ball_Y) > w_Paddle_Y_P2_Top) then r_SM_Main <= s_P1_Wins; end if; end if; when s_P1_Wins => if r_P1_Score = c_Score_Limit then r_P1_Score <= 0; else r_P1_Score <= r_P1_Score + 1; end if; r_SM_Main <= s_Cleanup; when s_P2_Wins => if r_P2_Score = c_Score_Limit then r_P2_Score <= 0; else r_P2_Score <= r_P2_Score + 1; end if; r_SM_Main <= s_Cleanup; when s_Cleanup => r_SM_Main <= s_Idle; when others => r_SM_Main <= s_Idle; end case; end if; end process p_SM_Main; -- Conditional Assignment of Game Active based on State Machine w_Game_Active <= '1' when r_SM_Main = s_Running else '0'; w_Draw_Any <= w_Draw_Ball or w_Draw_Paddle_P1 or w_Draw_Paddle_P2; -- Assign Color outputs, only two colors, White or Black o_Red_Video <= (others => '1') when w_Draw_Any = '1' else (others => '0'); o_Blu_Video <= (others => '1') when w_Draw_Any = '1' else (others => '0'); o_Grn_Video <= (others => '1') when w_Draw_Any = '1' else (others => '0'); end architecture rtl;
Pong_Paddle_Ctrl.vhd:
-- This module is designed for 640x480 with a 25 MHz input clock. -- Signal o_Paddle_Y represents the index of the top of the paddle in Y dimension. library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library work; use work.Pong_Pkg.all; entity Pong_Paddle_Ctrl is generic ( g_Player_Paddle_X : integer -- Changes for P1 vs P2 ); port ( i_Clk : in std_logic; i_Col_Count_Div : in std_logic_vector(5 downto 0); i_Row_Count_Div : in std_logic_vector(5 downto 0); -- Player Paddle Control i_Paddle_Up : in std_logic; i_Paddle_Dn : in std_logic; o_Draw_Paddle : out std_logic; o_Paddle_Y : out std_logic_vector(5 downto 0) ); end entity Pong_Paddle_Ctrl; architecture rtl of Pong_Paddle_Ctrl is -- Integer representation of the above 6 downto 0 counters. -- Integers are easier to work with conceptually signal w_Col_Index : integer range 0 to 2**i_Col_Count_Div'length := 0; signal w_Row_Index : integer range 0 to 2**i_Row_Count_Div'length := 0; signal w_Paddle_Count_En : std_logic; signal r_Paddle_Count : integer range 0 to c_Paddle_Speed := 0; -- Start Location (Top Left) of Paddles signal r_Paddle_Y : integer range 0 to c_Game_Height-c_Paddle_Height-1 := 0; signal r_Draw_Paddle : std_logic := '0'; begin w_Col_Index <= to_integer(unsigned(i_Col_Count_Div)); w_Row_Index <= to_integer(unsigned(i_Row_Count_Div)); -- Only allow paddles to move if only one button is pushed. w_Paddle_Count_En <= i_Paddle_Up xor i_Paddle_Dn; -- Controls how the paddles are moved. Sets r_Paddle_Y. -- Can change the movement speed by changing the constant in Package file. p_Move_Paddles : process (i_Clk) is begin if rising_edge(i_Clk) then -- Update the paddle counter when either switch is pushed and held. if w_Paddle_Count_En = '1' then if r_Paddle_Count = c_Paddle_Speed then r_Paddle_Count <= 0; else r_Paddle_Count <= r_Paddle_Count + 1; end if; else r_Paddle_Count <= 0; end if; -- Update the Paddle Location Slowly, only allowed when the Paddle Count -- reaches its limit if (i_Paddle_Up = '1' and r_Paddle_Count = c_Paddle_Speed) then -- If Paddle is already at the top, do not update it if r_Paddle_Y /= 0 then r_Paddle_Y <= r_Paddle_Y - 1; end if; elsif (i_Paddle_Dn = '1' and r_Paddle_Count = c_Paddle_Speed) then -- If Paddle is already at the bottom, do not update it if r_Paddle_Y /= c_Game_Height-c_Paddle_Height-1 then r_Paddle_Y <= r_Paddle_Y + 1; end if; end if; end if; end process p_Move_Paddles; -- Draws the Paddles as deteremined by input Generic g_Player_Paddle_X -- as well as r_Paddle_Y. p_Draw_Paddles : process (i_Clk) is begin if rising_edge(i_Clk) then -- Draws in a single column and in a range of rows. -- Range of rows is determined by c_Paddle_Height if (w_Col_Index = g_Player_Paddle_X and w_Row_Index >= r_Paddle_Y and w_Row_Index <= r_Paddle_Y + c_Paddle_Height) then r_Draw_Paddle <= '1'; else r_Draw_Paddle <= '0'; end if; end if; end process p_Draw_Paddles; -- Assign output for next higher module to use o_Draw_Paddle <= r_Draw_Paddle; o_Paddle_Y <= std_logic_vector(to_unsigned(r_Paddle_Y, o_Paddle_Y'length)); end architecture rtl;
Pong_Ball_Ctrl.vhd:
-- This module is designed for 640x480 with a 25 MHz input clock. library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library work; use work.Pong_Pkg.all; entity Pong_Ball_Ctrl is port ( i_Clk : in std_logic; i_Game_Active : in std_logic; i_Col_Count_Div : in std_logic_vector(5 downto 0); i_Row_Count_Div : in std_logic_vector(5 downto 0); -- o_Draw_Ball : out std_logic; o_Ball_X : out std_logic_vector(5 downto 0); o_Ball_Y : out std_logic_vector(5 downto 0) ); end entity Pong_Ball_Ctrl; architecture rtl of Pong_Ball_Ctrl is -- Integer representation of the above 6 downto 0 counters. -- Integers are easier to work with conceptually signal w_Col_Index : integer range 0 to 2**i_Col_Count_Div'length := 0; signal w_Row_Index : integer range 0 to 2**i_Row_Count_Div'length := 0; signal r_Ball_Count : integer range 0 to c_Ball_Speed := 0; -- X and Y location (Col, Row) for Pong Ball, also Previous Locations signal r_Ball_X : integer range 0 to 2**i_Col_Count_Div'length := 0; signal r_Ball_Y : integer range 0 to 2**i_Row_Count_Div'length := 0; signal r_Ball_X_Prev : integer range 0 to 2**i_Col_Count_Div'length := 0; signal r_Ball_Y_Prev : integer range 0 to 2**i_Row_Count_Div'length := 0; signal r_Draw_Ball : std_logic := '0'; begin w_Col_Index <= to_integer(unsigned(i_Col_Count_Div)); w_Row_Index <= to_integer(unsigned(i_Row_Count_Div)); p_Move_Ball : process (i_Clk) is begin if rising_edge(i_Clk) then -- If the game is not active, ball stays in the middle of the screen -- until the game starts. if i_Game_Active = '0' then r_Ball_X <= c_Game_Width/2; r_Ball_Y <= c_Game_Height/2; r_Ball_X_Prev <= c_Game_Width/2 + 1; r_Ball_Y_Prev <= c_Game_Height/2 - 1; else -- Update the ball counter continuously. Ball movement update rate is -- determined by a constant in the package file. if r_Ball_Count = c_Ball_Speed then r_Ball_Count <= 0; else r_Ball_Count <= r_Ball_Count + 1; end if; ----------------------------------------------------------------------- -- Control X Position (Col) ----------------------------------------------------------------------- if r_Ball_Count = c_Ball_Speed then -- Store Previous Location to keep track of ball movement r_Ball_X_Prev <= r_Ball_X; -- If ball is moving to the right, keep it moving right, but check -- that it's not at the wall (in which case it bounces back) if r_Ball_X_Prev < r_Ball_X then if r_Ball_X = c_Game_Width-1 then r_Ball_X <= r_Ball_X - 1; else r_Ball_X <= r_Ball_X + 1; end if; -- Ball is moving left, keep it moving left, check for wall impact elsif r_Ball_X_Prev > r_Ball_X then if r_Ball_X = 0 then r_Ball_X <= r_Ball_X + 1; else r_Ball_X <= r_Ball_X - 1; end if; end if; end if; ----------------------------------------------------------------------- -- Control Y Position (Row) ----------------------------------------------------------------------- if r_Ball_Count = c_Ball_Speed then -- Store Previous Location to keep track of ball movement r_Ball_Y_Prev <= r_Ball_Y; -- If ball is moving to the up, keep it moving up, but check -- that it's not at the wall (in which case it bounces back) if r_Ball_Y_Prev < r_Ball_Y then if r_Ball_Y = c_Game_Height-1 then r_Ball_Y <= r_Ball_Y - 1; else r_Ball_Y <= r_Ball_Y + 1; end if; -- Ball is moving down, keep it moving down, check for wall impact elsif r_Ball_Y_Prev > r_Ball_Y then if r_Ball_Y = 0 then r_Ball_Y <= r_Ball_Y + 1; else r_Ball_Y <= r_Ball_Y - 1; end if; end if; end if; end if; -- w_Game_Active = '1' end if; -- rising_edge(i_Clk) end process p_Move_Ball; -- Draws a ball at the location determined by X and Y indexes. p_Draw_Ball : process (i_Clk) is begin if rising_edge(i_Clk) then if (w_Col_Index = r_Ball_X and w_Row_Index = r_Ball_Y) then r_Draw_Ball <= '1'; else r_Draw_Ball <= '0'; end if; end if; end process p_Draw_Ball; o_Draw_Ball <= r_Draw_Ball; o_Ball_X <= std_logic_vector(to_unsigned(r_Ball_X, o_Ball_X'length)); o_Ball_Y <= std_logic_vector(to_unsigned(r_Ball_Y, o_Ball_Y'length)); end architecture rtl;
Pong_Pkg:
-- Package file containing all Constants and Components used in Pong Game library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; package Pong_Pkg is ----------------------------------------------------------------------------- -- Constants ----------------------------------------------------------------------------- -- Set the Width and Height of the Game Board constant c_Game_Width : integer := 40; constant c_Game_Height : integer := 30; -- Set the number of points to play to constant c_Score_Limit : integer := 9; -- Set the Height (in board game units) of the paddle. constant c_Paddle_Height : integer := 6; -- Set the Speed of the paddle movement. In this case, the paddle will move -- one board game unit every 50 milliseconds that the button is held down. constant c_Paddle_Speed : integer := 1250000; -- Set the Speed of the ball movement. In this case, the ball will move -- one board game unit every 50 milliseconds that the button is held down. constant c_Ball_Speed : integer := 1250000; -- Sets Column index to draw Player 1 & Player 2 Paddles. constant c_Paddle_Col_Location_P1 : integer := 0; constant c_Paddle_Col_Location_P2 : integer := c_Game_Width-1; ----------------------------------------------------------------------------- -- Component Declarations ----------------------------------------------------------------------------- component Pong_Paddle_Ctrl is generic ( g_Player_Paddle_X : integer -- Changes for P1 vs P2 ); port ( i_Clk : in std_logic; i_Col_Count_Div : in std_logic_vector(5 downto 0); i_Row_Count_Div : in std_logic_vector(5 downto 0); -- Player Paddle Control i_Paddle_Up : in std_logic; i_Paddle_Dn : in std_logic; o_Draw_Paddle : out std_logic; o_Paddle_Y : out std_logic_vector(5 downto 0) ); end component Pong_Paddle_Ctrl; component Pong_Ball_Ctrl is port ( i_Clk : in std_logic; i_Game_Active : in std_logic; i_Col_Count_Div : in std_logic_vector(5 downto 0); i_Row_Count_Div : in std_logic_vector(5 downto 0); -- o_Draw_Ball : out std_logic; o_Ball_X : out std_logic_vector(5 downto 0); o_Ball_Y : out std_logic_vector(5 downto 0) ); end component Pong_Ball_Ctrl; end package Pong_Pkg;
Project10_Pong_Top.v:
/////////////////////////////////////////////////////////////////////////// // File Downloaded From http://www.nandland.com /////////////////////////////////////////////////////////////////////////// module Project10_Pong_Top (input i_Clk, // Main Clock input i_UART_RX, // UART RX Data // Push BUttons input i_Switch_1, input i_Switch_2, input i_Switch_3, input i_Switch_4, // VGA output o_VGA_HSync, output o_VGA_VSync, output o_VGA_Red_0, output o_VGA_Red_1, output o_VGA_Red_2, output o_VGA_Grn_0, output o_VGA_Grn_1, output o_VGA_Grn_2, output o_VGA_Blu_0, output o_VGA_Blu_1, output o_VGA_Blu_2 ); // VGA Constants to set Frame Size parameter c_VIDEO_WIDTH = 3; parameter c_TOTAL_COLS = 800; parameter c_TOTAL_ROWS = 525; parameter c_ACTIVE_COLS = 640; parameter c_ACTIVE_ROWS = 480; // Common VGA Signals wire [c_VIDEO_WIDTH-1:0] w_Red_Video_Pong, w_Red_Video_Porch; wire [c_VIDEO_WIDTH-1:0] w_Grn_Video_Pong, w_Grn_Video_Porch; wire [c_VIDEO_WIDTH-1:0] w_Blu_Video_Pong, w_Blu_Video_Porch; // 25,000,000 / 115,200 = 217 UART_RX #(.CLKS_PER_BIT(217)) UART_RX_Inst (.i_Clock(i_Clk), .i_RX_Serial(i_UART_RX), .o_RX_DV(w_RX_DV), .o_RX_Byte()); // Generates Sync Pulses to run VGA VGA_Sync_Pulses #(.TOTAL_COLS(c_TOTAL_COLS), .TOTAL_ROWS(c_TOTAL_ROWS), .ACTIVE_COLS(c_ACTIVE_COLS), .ACTIVE_ROWS(c_ACTIVE_ROWS)) VGA_Sync_Pulses_Inst (.i_Clk(i_Clk), .o_HSync(w_HSync_VGA), .o_VSync(w_VSync_VGA), .o_Col_Count(), .o_Row_Count() ); // Debounce All Switches Debounce_Switch Switch_1 (.i_Clk(i_Clk), .i_Switch(i_Switch_1), .o_Switch(w_Switch_1)); Debounce_Switch Switch_2 (.i_Clk(i_Clk), .i_Switch(i_Switch_2), .o_Switch(w_Switch_2)); Debounce_Switch Switch_3 (.i_Clk(i_Clk), .i_Switch(i_Switch_3), .o_Switch(w_Switch_3)); Debounce_Switch Switch_4 (.i_Clk(i_Clk), .i_Switch(i_Switch_4), .o_Switch(w_Switch_4)); Pong_Top #(.c_TOTAL_COLS(c_TOTAL_COLS), .c_TOTAL_ROWS(c_TOTAL_ROWS), .c_ACTIVE_COLS(c_ACTIVE_COLS), .c_ACTIVE_ROWS(c_ACTIVE_ROWS)) Pong_Inst (.i_Clk(i_Clk), .i_HSync(w_HSync_VGA), .i_VSync(w_VSync_VGA), .i_Game_Start(w_RX_DV), .i_Paddle_Up_P1(w_Switch_1), .i_Paddle_Dn_P1(w_Switch_2), .i_Paddle_Up_P2(w_Switch_3), .i_Paddle_Dn_P2(w_Switch_4), .o_HSync(w_HSync_Pong), .o_VSync(w_VSync_Pong), .o_Red_Video(w_Red_Video_Pong), .o_Grn_Video(w_Grn_Video_Pong), .o_Blu_Video(w_Blu_Video_Pong)); VGA_Sync_Porch #(.VIDEO_WIDTH(c_VIDEO_WIDTH), .TOTAL_COLS(c_TOTAL_COLS), .TOTAL_ROWS(c_TOTAL_ROWS), .ACTIVE_COLS(c_ACTIVE_COLS), .ACTIVE_ROWS(c_ACTIVE_ROWS)) VGA_Sync_Porch_Inst (.i_Clk(i_Clk), .i_HSync(w_HSync_Pong), .i_VSync(w_VSync_Pong), .i_Red_Video(w_Red_Video_Pong), .i_Grn_Video(w_Grn_Video_Pong), .i_Blu_Video(w_Blu_Video_Pong), .o_HSync(o_VGA_HSync), .o_VSync(o_VGA_VSync), .o_Red_Video(w_Red_Video_Porch), .o_Grn_Video(w_Grn_Video_Porch), .o_Blu_Video(w_Blu_Video_Porch)); assign o_VGA_Red_0 = w_Red_Video_Porch[0]; assign o_VGA_Red_1 = w_Red_Video_Porch[1]; assign o_VGA_Red_2 = w_Red_Video_Porch[2]; assign o_VGA_Grn_0 = w_Grn_Video_Porch[0]; assign o_VGA_Grn_1 = w_Grn_Video_Porch[1]; assign o_VGA_Grn_2 = w_Grn_Video_Porch[2]; assign o_VGA_Blu_0 = w_Blu_Video_Porch[0]; assign o_VGA_Blu_1 = w_Blu_Video_Porch[1]; assign o_VGA_Blu_2 = w_Blu_Video_Porch[2]; endmodule
Pong_Top.v:
module Pong_Top #(parameter c_TOTAL_COLS=800, parameter c_TOTAL_ROWS=525, parameter c_ACTIVE_COLS=640, parameter c_ACTIVE_ROWS=480) (input i_Clk, input i_HSync, input i_VSync, // Game Start Button input i_Game_Start, // Player 1 and Player 2 Controls (Controls Paddles) input i_Paddle_Up_P1, input i_Paddle_Dn_P1, input i_Paddle_Up_P2, input i_Paddle_Dn_P2, // Output Video output reg o_HSync, output reg o_VSync, output [3:0] o_Red_Video, output [3:0] o_Grn_Video, output [3:0] o_Blu_Video); // Local Constants to Determine Game Play parameter c_GAME_WIDTH = 40; parameter c_GAME_HEIGHT = 30; parameter c_SCORE_LIMIT = 9; parameter c_PADDLE_HEIGHT = 6; parameter c_PADDLE_COL_P1 = 0; // Col Index of Paddle for P1 parameter c_PADDLE_COL_P2 = c_GAME_WIDTH-1; // Index for P2 // State machine enumerations parameter IDLE = 3'b000; parameter RUNNING = 3'b001; parameter P1_WINS = 3'b010; parameter P2_WINS = 3'b011; parameter CLEANUP = 3'b100; reg [2:0] r_SM_Main = IDLE; wire w_HSync, w_VSync; wire [9:0] w_Col_Count, w_Row_Count; wire w_Draw_Paddle_P1, w_Draw_Paddle_P2; wire [5:0] w_Paddle_Y_P1, w_Paddle_Y_P2; wire w_Draw_Ball, w_Draw_Any; wire [5:0] w_Ball_X, w_Ball_Y; reg [3:0] r_P1_Score = 0; reg [3:0] r_P2_Score = 0; // Divided version of the Row/Col Counters // Allows us to make the board 40x30 wire [5:0] w_Col_Count_Div, w_Row_Count_Div; Sync_To_Count #(.TOTAL_COLS(c_TOTAL_COLS), .TOTAL_ROWS(c_TOTAL_ROWS)) Sync_To_Count_Inst (.i_Clk(i_Clk), .i_HSync(i_HSync), .i_VSync(i_VSync), .o_HSync(w_HSync), .o_VSync(w_VSync), .o_Col_Count(w_Col_Count), .o_Row_Count(w_Row_Count)); // Register syncs to align with output data. always @(posedge i_Clk) begin o_HSync <= w_HSync; o_VSync <= w_VSync; end // Drop 4 LSBs, which effectively divides by 16 assign w_Col_Count_Div = w_Col_Count[9:4]; assign w_Row_Count_Div = w_Row_Count[9:4]; // Instantiation of Paddle Control + Draw for Player 1 Pong_Paddle_Ctrl #(.c_PLAYER_PADDLE_X(c_PADDLE_COL_P1), .c_GAME_HEIGHT(c_GAME_HEIGHT)) P1_Inst (.i_Clk(i_Clk), .i_Col_Count_Div(w_Col_Count_Div), .i_Row_Count_Div(w_Row_Count_Div), .i_Paddle_Up(i_Paddle_Up_P1), .i_Paddle_Dn(i_Paddle_Dn_P1), .o_Draw_Paddle(w_Draw_Paddle_P1), .o_Paddle_Y(w_Paddle_Y_P1)); // Instantiation of Paddle Control + Draw for Player 2 Pong_Paddle_Ctrl #(.c_PLAYER_PADDLE_X(c_PADDLE_COL_P2), .c_GAME_HEIGHT(c_GAME_HEIGHT)) P2_Inst (.i_Clk(i_Clk), .i_Col_Count_Div(w_Col_Count_Div), .i_Row_Count_Div(w_Row_Count_Div), .i_Paddle_Up(i_Paddle_Up_P2), .i_Paddle_Dn(i_Paddle_Dn_P2), .o_Draw_Paddle(w_Draw_Paddle_P2), .o_Paddle_Y(w_Paddle_Y_P2)); // Instantiation of Ball Control + Draw Pong_Ball_Ctrl Pong_Ball_Ctrl_Inst (.i_Clk(i_Clk), .i_Game_Active(w_Game_Active), .i_Col_Count_Div(w_Col_Count_Div), .i_Row_Count_Div(w_Row_Count_Div), .o_Draw_Ball(w_Draw_Ball), .o_Ball_X(w_Ball_X), .o_Ball_Y(w_Ball_Y)); // Create a state machine to control the state of play always @(posedge i_Clk) begin case (r_SM_Main) // Stay in this state until Game Start button is hit IDLE : begin if (i_Game_Start == 1'b1) r_SM_Main <= RUNNING; end // Stay in this state until either player misses the ball // can only occur when the Ball is at 0 to c_GAME_WIDTH-1 RUNNING : begin // Player 1 Side if (w_Ball_X == 0 && (w_Ball_Y < w_Paddle_Y_P1 || w_Ball_Y > w_Paddle_Y_P1 + c_PADDLE_HEIGHT)) r_SM_Main <= P2_WINS; // Player 2 Side else if (w_Ball_X == c_GAME_WIDTH-1 && (w_Ball_Y < w_Paddle_Y_P2 || w_Ball_Y > w_Paddle_Y_P2 + c_PADDLE_HEIGHT)) r_SM_Main <= P1_WINS; end P1_WINS : begin if (r_P1_Score == c_SCORE_LIMIT-1) r_P1_Score <= 0; else begin r_P1_Score <= r_P1_Score + 1; r_SM_Main <= CLEANUP; end end P2_WINS : begin if (r_P2_Score == c_SCORE_LIMIT-1) r_P2_Score <= 0; else begin r_P2_Score <= r_P2_Score + 1; r_SM_Main <= CLEANUP; end end CLEANUP : r_SM_Main <= IDLE; endcase end // Conditional Assignment based on State Machine state assign w_Game_Active = (r_SM_Main == RUNNING) ? 1'b1 : 1'b0; assign w_Draw_Any = w_Draw_Ball | w_Draw_Paddle_P1 | w_Draw_Paddle_P2; // Assign colors. Currently set to only 2 colors, white or black. assign o_Red_Video = w_Draw_Any ? 4'b1111 : 4'b0000; assign o_Grn_Video = w_Draw_Any ? 4'b1111 : 4'b0000; assign o_Blu_Video = w_Draw_Any ? 4'b1111 : 4'b0000; endmodule // Pong_Top
Pong_Paddle_Ctrl.v:
module Pong_Paddle_Ctrl #(parameter c_PLAYER_PADDLE_X=0, parameter c_PADDLE_HEIGHT=6, parameter c_GAME_HEIGHT=30) (input i_Clk, input [5:0] i_Col_Count_Div, input [5:0] i_Row_Count_Div, input i_Paddle_Up, input i_Paddle_Dn, output reg o_Draw_Paddle, output reg [5:0] o_Paddle_Y); // Set the Speed of the paddle movement. // In this case, the paddle will move one board game unit // every 50 milliseconds that the button is held down. parameter c_PADDLE_SPEED = 1250000; reg [31:0] r_Paddle_Count = 0; wire w_Paddle_Count_En; // Only allow paddles to move if only one button is pushed. // ^ is an XOR bitwise operation. assign w_Paddle_Count_En = i_Paddle_Up ^ i_Paddle_Dn; always @(posedge i_Clk) begin if (w_Paddle_Count_En == 1'b1) begin if (r_Paddle_Count == c_PADDLE_SPEED) r_Paddle_Count <= 0; else r_Paddle_Count <= r_Paddle_Count + 1; end // Update the Paddle Location slowly. Only allowed when the // Paddle Count reaches its limit. Don't update if paddle is // already at the top of the screen. if (i_Paddle_Up == 1'b1 && r_Paddle_Count == c_PADDLE_SPEED && o_Paddle_Y !== 0) o_Paddle_Y <= o_Paddle_Y - 1; else if (i_Paddle_Dn == 1'b1 && r_Paddle_Count == c_PADDLE_SPEED && o_Paddle_Y !== c_GAME_HEIGHT-c_PADDLE_HEIGHT-1) o_Paddle_Y <= o_Paddle_Y + 1; end // Draws the Paddles as determined by input parameter // c_PLAYER_PADDLE_X as well as o_Paddle_Y always @(posedge i_Clk) begin // Draws in a single column and in a range of rows. // Range of rows is determined by c_PADDLE_HEIGHT if (i_Col_Count_Div == c_PLAYER_PADDLE_X && i_Row_Count_Div >= o_Paddle_Y && i_Row_Count_Div <= o_Paddle_Y + c_PADDLE_HEIGHT) o_Draw_Paddle <= 1'b1; else o_Draw_Paddle <= 1'b0; end endmodule // Pong_Paddle_Ctrl
Pong_Ball_Ctrl.v:
module Pong_Ball_Ctrl #(parameter c_GAME_WIDTH=40, parameter c_GAME_HEIGHT=30) (input i_Clk, input i_Game_Active, input [5:0] i_Col_Count_Div, input [5:0] i_Row_Count_Div, output reg o_Draw_Ball, output reg [5:0] o_Ball_X = 0, output reg [5:0] o_Ball_Y = 0); // Set the Speed of the ball movement. // In this case, the ball will move one board game unit // every 50 milliseconds that the button is held down. parameter c_BALL_SPEED = 1250000; reg [5:0] r_Ball_X_Prev = 0; reg [5:0] r_Ball_Y_Prev = 0; reg [31:0] r_Ball_Count = 0; always @(posedge i_Clk) begin // If the game is not active, ball stays in the middle of // screen until the game starts. if (i_Game_Active == 1'b0) begin o_Ball_X <= c_GAME_WIDTH/2; o_Ball_Y <= c_GAME_HEIGHT/2; r_Ball_X_Prev <= c_GAME_WIDTH/2 + 1; r_Ball_Y_Prev <= c_GAME_HEIGHT/2 - 1; end // Update the ball counter continuously. Ball movement // update rate is determined by input parameter // If ball counter is at its limit, update the ball position // in both X and Y. else begin if (r_Ball_Count < c_BALL_SPEED) r_Ball_Count <= r_Ball_Count + 1; else begin r_Ball_Count <= 0; // Store Previous Location to keep track of movement r_Ball_X_Prev <= o_Ball_X; r_Ball_Y_Prev <= o_Ball_Y; // When Previous Value is less than current value, ball is moving // to right. Keep it moving to the right unless we are at wall. // When Prevous value is greater than current value, ball is moving // to left. Keep it moving to the left unless we are at a wall. // Same philosophy for both X and Y. if ((r_Ball_X_Prev < o_Ball_X && o_Ball_X == c_GAME_WIDTH-1) || (r_Ball_X_Prev > o_Ball_X && o_Ball_X != 0)) o_Ball_X <= o_Ball_X - 1; else o_Ball_X <= o_Ball_X + 1; if ((r_Ball_Y_Prev < o_Ball_Y && o_Ball_Y == c_GAME_HEIGHT-1) || (r_Ball_Y_Prev > o_Ball_Y && o_Ball_Y != 0)) o_Ball_Y <= o_Ball_Y - 1; else o_Ball_Y <= o_Ball_Y + 1; end end end // always @ (posedge i_Clk) // Draws a ball at the location determined by X and Y indexes. always @(posedge i_Clk) begin if (i_Col_Count_Div == o_Ball_X && i_Row_Count_Div == o_Ball_Y) o_Draw_Ball <= 1'b1; else o_Draw_Ball <= 1'b0; end endmodule // Pong_Ball_Ctrl
Congratulations! You’ve reached the end of the Go Board tutorials! I hope that you’ve enjoyed learning VHDL and Verilog with the Go Board. Please subscribe to my YouTube channel at YouTube. Lots more content is coming, so bookmark nandland.com and check back often.
Leave A Comment