FPGA Tic Tac Toe
by RyanFrawley in Circuits > Microcontrollers
19584 Views, 46 Favorites, 0 Comments
FPGA Tic Tac Toe
"Tic Tac Toe? What's that? I've never heard of that."
- Nobody ever
By Ryan Frawley and Derek Nguyen
This guide will show you how to make a working Tic Tac Toe game in VHDL on a Nexys 2 FPGA board. This tutorial was done for part of a Cal Poly CPE 133 final project.
Parts list:
- RGB LED
http://www.amazon.com/microtivity-IL612-Diffused-C...
- Breadboards x3
http://www.amazon.com/microtivity-IB830-830-point-...
- Buttons
http://www.amazon.com/microtivity-IM206-6x6x6mm-Ta...
- Jumper Cable
http://www.amazon.com/Wosang-Solderless-Flexible-B...
- NEXYS 2
http://www.digilentinc.com/Products/Detail.cfm?Pro...
WARNING: We are assuming that you, the reader, have some kind of experience with Xilinx and basic understanding of Digital Design. If "YES!" then what you're about to read won't seem like a black box.
Single LED Controller
For this step, we will create a little controller for each individual LED.
We will be using the fundamental D - Flip Flop with a synchronous enable and an asynchronous reset.
Since our LED is the fancy RGB LED, it is wiser to use a 2 bit bus to determine which color gets turned on. To do so, we need 2 D Flip Flops -- one for each color of the LED.
Each of the flip flops will share a clock, the input of the button, and a reset button. However, each of the D flip flops will get a D value determined by the multi-LEDs controller.
The press of the button will latch in the D value of each flip flop and outputs it. The output will be a 2 bit bus formed by the individual Q output of each flip flop and another 2 bit bus formed by the Q inverse of each flip flop.
The 2 bit bus consisting of the Q inverse output will be directly connected to the LED output signal. Meanwhile, the 2 bit bus consisting of the Q output will go to the multi-LEDs controller to determine which player gets to go next.
We will be using 9 of these flip flops for each button.
entity Single_LED_Controller is port (Clock : in std_logic; Button : in std_logic; Reset : in std_logic; Player1 : in std_logic; Player2 : in std_logic; LED_Output : out std_logic_vector(1 downto 0); Handle_Input : out std_logic_vector(1 downto 0)); end Single_LED_Controller; architecture controller of Single_LED_Controller is component D_FlipFlop is port (Enable: in std_logic; D : in std_logic; Reset : in std_logic; Clock : in std_logic; Q : out std_logic); end component; signal temp1, temp2 : std_logic; begin D_FF_Main_1 : D_FlipFlop port map(Button, Player1, Reset, Clock, temp1); D_FF_Main_2 : D_FlipFlop port map(Button, Player2, Reset, Clock, temp2); LED_Output(0) <= not(temp1); LED_Output(1) <= not(temp2); Handle_Input(0) <= temp1; Handle_Input(1) <= temp2; end controller;
Configure Your Multi-LEDs Controller
Determining who gets to go next is simple.
Remember the good old T Flip Flop that let the user “toggle” the output value between 1 and 0? We won’t be using that. With the Nexys 2 Board's “advanced” clock speed, life will be very difficult for you if you ever decide to use that for this game.
Another major flaw of using the T Flip flop is that we have to make sure the user can’t change the output value once he or she already pressed the button -- that’s a no no.
Therefore, for this design, we have decided to use XOR logic gates to do the function of switching between each players. The fundamental idea of XOR is that it will output ‘1’ if there is an odd number of 1's and output a ‘0’ if there is an even number of 1's. We will be using that idea to determine which player's turn it is.
For the first layer of XOR gates, their input will be the 2 bit Q output of the D-flip flops setup in the single LED controller. Each of these XOR gates correspond to a button of our Tic Tac Toe game.
If you take a look at the schematic, the output of the module will be a 2 bit output: Index 0 will be the direct output of the XOR and Index 1 will be its inverse. This combination of “10”, red, and “01”, blue, will be switching back and forth determine on the number of buttons that have been pressed.
The output of the XOR will be feed straight to each individual Single LED Controller for the D’s input of the D flip flops set up. Since we will have either a “01” or “10” output of the D flip flops setup, case “11” or “00” will never be the input for our XOR setup.
WOW! That was a lot to explain. But trust us, it is infinitely easier than going with the T flip flop or the linear shift register route. YUCK!
entity Multi_LED_Handle is port (Reset : in std_logic; Input1 : in std_logic_vector(1 downto 0); Input2 : in std_logic_vector(1 downto 0); Input3 : in std_logic_vector(1 downto 0); Input4 : in std_logic_vector(1 downto 0); Input5 : in std_logic_vector(1 downto 0); Input6 : in std_logic_vector(1 downto 0); Input7 : in std_logic_vector(1 downto 0); Input8 : in std_logic_vector(1 downto 0); Input9 : in std_logic_vector(1 downto 0); Output : out std_logic_vector(1 downto 0)); end Multi_LED_Handle; architecture Dataflow of Multi_LED_Handle is signal temp : std_logic_vector(9 downto 0); begin -- XOR First level temp(0) <= input1(0) XOR input1(1); temp(1) <= input2(0) XOR input2(1); temp(2) <= input3(0) XOR input3(1); temp(3) <= input4(0) XOR input4(1); temp(4) <= input5(0) XOR input5(1); temp(5) <= input6(0) XOR input6(1); temp(6) <= input7(0) XOR input7(1); temp(7) <= input8(0) XOR input8(1); temp(8) <= input9(0) XOR input9(1); -- XOR Second level temp(9) <= temp(0) XOR temp(1) XOR temp(2) XOR temp(3) XOR temp(4) XOR temp(5) XOR temp(6) XOR temp(7) XOR temp(8); Output(0) <= temp(9); Output(1) <= not(temp(9)); end Dataflow;
Victory Conditions Module
Now that we have the LEDs and buttons working, we need to tell the board to look for win conditions (or lack thereof). In order to do this, we will be using a couple big "if" statements inside of processes.
The first process checks all 8 possible wins (3 horizontal, 3 vertical, and 2 diagonal) for each player for a total of 16 if statements.
process (in1t, in2t, in3t, in4t, in5t, in6t, in7t, in8t, in9t) is begin --player 1 conditions -- horizontal if (in1t(1) = '1' and in2t(1) = '1' and in3t(1) = '1') then p1 <= '1'; elsif (in4t(1) = '1' and in5t(1) = '1' and in6t(1) = '1') then p1 <= '1'; elsif (in7t(1) = '1' and in8t(1) = '1' and in9t(1) = '1') then p1 <= '1'; -- vertical elsif (in1t(1) = '1' and in4t(1) = '1' and in7t(1) = '1') then p1 <= '1'; elsif (in2t(1) = '1' and in5t(1) = '1' and in8t(1) = '1') then p1 <= '1'; elsif (in3t(1) = '1' and in6t(1) = '1' and in9t(1) = '1') then p1 <= '1'; -- diagonal elsif (in1t(1) = '1' and in5t(1) = '1' and in9t(1) = '1') then p1 <= '1'; elsif (in3t(1) = '1' and in5t(1) = '1' and in7t(1) = '1') then p1 <= '1'; else p1 <= '0'; end if; -- player 2 conditions -- horizontal if (in1t(0) = '1' and in2t(0) = '1' and in3t(0) = '1') then p2 <= '1'; elsif (in4t(0) = '1' and in5t(0) = '1' and in6t(0) = '1') then p2 <= '1'; elsif (in7t(0) = '1' and in8t(0) = '1' and in9t(0) = '1') then p2 <= '1'; -- vertical elsif (in1t(0) = '1' and in4t(0) = '1' and in7t(0) = '1') then p2 <= '1'; elsif (in2t(0) = '1' and in5t(0) = '1' and in8t(0) = '1') then p2 <= '1'; elsif (in3t(0) = '1' and in6t(0) = '1' and in9t(0) = '1') then p2 <= '1'; -- diagonal elsif (in1t(0) = '1' and in5t(0) = '1' and in9t(0) = '1') then p2 <= '1'; elsif (in3t(0) = '1' and in5t(0) = '1' and in7t(0) = '1') then p2 <= '1'; else p2 <= '0'; end if; end process;
Next, we must make a much smaller second process that compares the P1 and P2 win state to determine if there was a tie.
process (P1, P2) is<br>begin if (P1 = '0' and P2 = '0') then T <= '1'; else T <= '0'; end if; end process;
Finally, we must pass the values stored in the signals to our outputs.
p1win <= P1; p2win <= P2; tie <= t;
7 Segment Decoder Module
We will be using the 7 segment display on the Nexys 2 to display whose turn it is. In order to do this, we'll use a simple 7 segment decoder.
entity Seg is Port ( Turn : in STD_LOGIC_VECTOR (1 downto 0); Clock : in STD_LOGIC; Anode : out STD_LOGIC_VECTOR (3 downto 0); Segment : out STD_LOGIC_VECTOR (7 downto 0)); end Seg; architecture Behavioral of Seg is signal Seg_temp : std_logic_vector(7 downto 0) := "11111111"; begin anode <= "1110"; process (turn, clock) is begin if (rising_edge(clock)) then if (turn = "01") then seg_temp <= "10011111"; elsif (turn = "10") then seg_temp <= "00100101"; else null; end if; end if; end process; segment <= seg_temp; end Behavioral;
As you can see, we started out by enabling only the last anode. After that, we check the current turn and change the segment output accordingly. For segment mapping, check out the Nexys 2 reference manual or the Nexys 3 reference manual.
Main Game Module
The VHDL section of the game is almost complete! All we have to do now is connect all of the modules we just wrote. First, we need to declare all of our inputs and outputs. We have 9 separate LED outputs o1-o9 plus one extra (o10) that lights up to show who won at the end of the game. There is also a reset button that clears the board once the game ends.
entity Main_Game_Board is port (CLK : in std_logic; BTN : in std_logic_vector(8 downto 0); MAIN_RESET : in std_logic; o1 : out std_logic_vector(1 downto 0); o2 : out std_logic_vector(1 downto 0); o3 : out std_logic_vector(1 downto 0); o4 : out std_logic_vector(1 downto 0); o5 : out std_logic_vector(1 downto 0); o6 : out std_logic_vector(1 downto 0); o7 : out std_logic_vector(1 downto 0); o8 : out std_logic_vector(1 downto 0); o9 : out std_logic_vector(1 downto 0); o10 : out std_logic_vector(1 downto 0); MAIN_AN : out STD_LOGIC_VECTOR(3 downto 0); MAIN_SEG : out STD_LOGIC_VECTOR(7 downto 0)); end Main_Game_Board
Next, we need to integrate all of the modules we wrote. In order to do this, we'll use a bunch of components.
component Single_LED_Controller is port (Clock : in std_logic; Button : in std_logic; Reset : in std_logic; Player1 : in std_logic; Player2 : in std_logic; LED_Output : out std_logic_vector(1 downto 0); Handle_Input : out std_logic_vector(1 downto 0)); end component; component Multi_LED_Handle is port (Reset : in std_logic; Input1 : in std_logic_vector(1 downto 0); Input2 : in std_logic_vector(1 downto 0); Input3 : in std_logic_vector(1 downto 0); Input4 : in std_logic_vector(1 downto 0); Input5 : in std_logic_vector(1 downto 0); Input6 : in std_logic_vector(1 downto 0); Input7 : in std_logic_vector(1 downto 0); Input8 : in std_logic_vector(1 downto 0); Input9 : in std_logic_vector(1 downto 0); Output : out std_logic_vector(1 downto 0)); end component; component Victory is Port ( in1 : in STD_LOGIC_VECTOR(1 downto 0); in2 : in STD_LOGIC_VECTOR(1 downto 0); in3 : in STD_LOGIC_VECTOR(1 downto 0); in4 : in STD_LOGIC_VECTOR(1 downto 0); in5 : in STD_LOGIC_VECTOR(1 downto 0); in6 : in STD_LOGIC_VECTOR(1 downto 0); in7 : in STD_LOGIC_VECTOR(1 downto 0); in8 : in STD_LOGIC_VECTOR(1 downto 0); in9 : in STD_LOGIC_VECTOR(1 downto 0); Tie : out STD_LOGIC; P1win : out STD_LOGIC; P2win : out STD_LOGIC); end component; component Seg is Port ( Turn : in STD_LOGIC_VECTOR (1 downto 0); Clock : in STD_LOGIC; Anode : out STD_LOGIC_VECTOR (3 downto 0); Segment : out STD_LOGIC_VECTOR (7 downto 0)); end component; component End_Tie is port (Button : in std_logic_vector(8 downto 0); Reset : in std_logic; clock : in std_logic; Judgement : out std_logic); end component;
Now all that's left is to create signals and pass them between the inputs and outputs of the modules we wrote. After the signals go through all of the modules, they are sent to the outputs of the main game module.
signal PL: std_logic_vector(1 downto 0) := "10"; signal H1, H2, H3, H4, H5, H6, H7, H8, H9 : std_logic_vector(1 downto 0) := "00"; signal T, P1, P2 : std_logic; signal An : STD_LOGIC_VECTOR (3 downto 0); signal Seg_temp : STD_LOGIC_VECTOR (7 downto 0); signal Bn : std_logic_vector (8 downto 0); signal Judgement_temp : std_logic; begin process(T, BTN, CLK) is begin if (rising_edge(CLK)) then if (T = '0' or Judgement_temp = '1') then Bn <= "000000000"; else Bn(0) <= BTN(0); Bn(1) <= BTN(1); Bn(2) <= BTN(2); Bn(3) <= BTN(3); Bn(4) <= BTN(4); Bn(5) <= BTN(5); Bn(6) <= BTN(6); Bn(7) <= BTN(7); Bn(8) <= BTN(8); end if; end if; end process;</p><p> LED1 : Single_LED_Controller port map (CLK, BN(0), MAIN_RESET, PL(0), PL(1), o1, H1); LED2 : Single_LED_Controller port map (CLK, BN(1), MAIN_RESET, PL(0), PL(1), o2, H2); LED3 : Single_LED_Controller port map (CLK, BN(2), MAIN_RESET, PL(0), PL(1), o3, H3); LED4 : Single_LED_Controller port map (CLK, BN(3), MAIN_RESET, PL(0), PL(1), o4, H4); LED5 : Single_LED_Controller port map (CLK, BN(4), MAIN_RESET, PL(0), PL(1), o5, H5); LED6 : Single_LED_Controller port map (CLK, BN(5), MAIN_RESET, PL(0), PL(1), o6, H6); LED7 : Single_LED_Controller port map (CLK, BN(6), MAIN_RESET, PL(0), PL(1), o7, H7); LED8 : Single_LED_Controller port map (CLK, BN(7), MAIN_RESET, PL(0), PL(1), o8, H8); LED9 : Single_LED_Controller port map (CLK, BN(8), MAIN_RESET, PL(0), PL(1), o9, H9); MLEDH : Multi_LED_Handle port map(MAIN_RESET, H1, H2, H3, H4, H5, H6, H7, H8, H9, PL); Victory_module : victory port map(H1, H2, H3, H4, H5, H6, H7, H8, H9, T, P1, P2); display : seg port map(PL, CLK, An, Seg_temp); End_game : End_tie port map(Bn, MAIN_RESET, CLK, Judgement_temp); process(T, P1, P2, An, Seg_temp, Judgement_temp, CLK) is begin if (rising_edge(CLK)) then if(T = '1') then o10 <= "00"; MAIN_AN <= an; MAIN_SEG <= Seg_temp; elsif(P1 = '1') then o10 <= "01"; MAIN_AN <= "1111"; MAIN_SEG <= "11111111"; elsif(P2 = '1') then o10 <= "10"; MAIN_AN <= "1111"; MAIN_SEG <= "11111111"; else o10 <= "11"; end if; if(Judgement_temp = '1') then MAIN_AN <= "1111"; MAIN_SEG <= "11111111"; end if; end if; end process;
Ending the Game With a Tie
This module is different. We considered this to be the last baby step before you can run free.
What happens when we end with a tie? Do you want the user to continue changing the input?
No, we do not want the user to change his or her input. That would be cheating!
To prevent the user to change their input once the game ends with a tie and no buttons are left to press, we will be using 9 D flip flops with D set to ‘1’ automatically to determine if the buttons have been pressed or not.
Then, using the AND gate, we will “And” all of the outputs of the D flip flops together. The module’s output will get the result of this AND gate.
In the Main Game board module, If the output of the END-Tie module equals to ‘1’ then the button will all get the value of ‘0’. It means that the user won’t be able to press any button and have it register.
entity End_Tie is port (Button : in std_logic_vector(8 downto 0); Reset : in std_logic; clock : in std_logic; Judgement : out std_logic); end End_Tie; architecture Behavioral of End_Tie is component D_FlipFlop is port (Enable: in std_logic; D : in std_logic; Reset : in std_logic; Clock : in std_logic; Q : out std_logic); end component; signal btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8 : std_logic; begin D_FF0 : D_FlipFlop port map(Button(0), '1', Reset, Clock, btn0); D_FF1 : D_FlipFlop port map(Button(1), '1', Reset, Clock, btn1); D_FF2 : D_FlipFlop port map(Button(2), '1', Reset, Clock, btn2); D_FF3 : D_FlipFlop port map(Button(3), '1', Reset, Clock, btn3); D_FF4 : D_FlipFlop port map(Button(4), '1', Reset, Clock, btn4); D_FF5 : D_FlipFlop port map(Button(5), '1', Reset, Clock, btn5); D_FF6 : D_FlipFlop port map(Button(6), '1', Reset, Clock, btn6); D_FF7 : D_FlipFlop port map(Button(7), '1', Reset, Clock, btn7); D_FF8 : D_FlipFlop port map(Button(8), '1', Reset, Clock, btn8); Judgement <= btn0 and btn1 and btn2 and btn3 and btn4 and btn5 and btn6 and btn7 and btn8; end Behavioral;
Connecting the LEDs
Now that the software part is complete, it's time to move on to the hardware of the game. To begin, we will need to connect an RGB LED to the PMOD port on the Nexys 2. The wires in the picture above are color coded to show which goes where. The black wire is the power which goes to the common anode of the LED. The red and blue wires are self explanatory.
On the "fun" side of things, please refer to the nexys 2's reference manual to determine the ground port of the PMOD. Remember this, do not plug the power cable or any cable to that port! The last time we did that, we burned out the LED...
LED #1, you will forever be missed.
Connecting the Buttons
Next, we need to connect all of the buttons to the PMOD ports on the Nexys 2. One side of the button gets power (green wire) and the other goes to the PMOD port (yellow wire).
Connections Complete
When all LEDs and buttons are connected, there will be a huge mess of wires. This will be a good test of your cable management skills!
"... or you could recreate a miniature Amazon rainforest on your breadboards." - Derek Nguyen
Try It for Yourself
Included in this step is a .zip file with all of the VHDL modules and the generated .bit file. If you already have the hardware built, you can skipped all of the software aspect and just run the .bit file with Adept. If you ever find any bug in the vhdl description, don't worry because this project will be updated every time we discover a new "break through".
Have fun!