You are on page 1of 85

ECSE 323: Digital System Design

Laboratory 5: System Integration

Payom Meshgin (260431193) Yi Qing Xiao (260429342)

Group #27
4/16/2012

Prof. Katarzyna Radecka

Table of Contents

TABLE OF CONTENTS
Table of Contents ..................................................................................................................... i Table of Figures ....................................................................................................................... v Introduction............................................................................................................................. 1 Lab 5: The Controller ai_control ............................................................................................... 3 Discussion of the design ....................................................................................................... 3 Description of the I/O pins .................................................................................................... 4 FSM schematics ................................................................................................................... 6 Pseudocode ........................................................................................................................ 11 Other figures ....................................................................................................................... 13 Reports ...............................................................................................................................14 Controller implementation ..................................................................................................14 Input pins: .......................................................................................................................14 Output pins: .................................................................................................................... 15 Simulation waveforms ........................................................................................................ 15 Discussion of Overall Design ...................................................................................................18 custom_types: Custom Data Types .................................................................................... 19 ttt: Overall Design .............................................................................................................. 20 Description .................................................................................................................... 20 Description of the I/O pins .............................................................................................. 20 Pin Assignments ............................................................................................................ 22 Reports ...........................................................................................................................23 Simulations and Waveforms........................................................................................... 24

ii

Table of Contents sys_led_state: LED Current State Display ............................................................................ 27 Description ..................................................................................................................... 27 Description of the I/O pins ............................................................................................... 27 Simulations and Waveforms........................................................................................... 28 Pseudocode ................................................................................................................... 28 GM_LED: SEVEN SEGMENT LED DISPLAY ................................................................ 29 DESCRIPTION ................................................................................................................ 29 DESCRIPTION OF THE I/O PINS ......................................................................................30 SIMULATIONS AND WAVEFORMS................................................................................. 31 Pseudocode ....................................................................................................................34 ga_array: Game Table ........................................................................................................ 36 Description .................................................................................................................... 36 Description of the I/O pins ............................................................................................... 37 Simulations and Waveforms............................................................................................38 Pseudocode ................................................................................................................... 39 ga_array_mux: Multiplexer for the Game Table .................................................................. 39 Description .................................................................................................................... 39 Description of the I/O pins .............................................................................................. 40 Simulations and Waveforms........................................................................................... 42 Pseudocode ....................................................................................................................43 gm_status: Check Current Game Status ............................................................................. 44 Description .................................................................................................................... 44 Description of the I/O pins .............................................................................................. 45 Simulations and Waveforms........................................................................................... 46

Table of Contents Pseudocode ................................................................................................................... 46 hp_wait: Wait for HP Move..................................................................................................47 Description .................................................................................................................... 48 Description of the I/O pins .............................................................................................. 48 Simulations and Waveforms........................................................................................... 49 Pseudocode ................................................................................................................... 50 ai_find_win: Try to Win Or Block ......................................................................................... 51 Description ..................................................................................................................... 51 Description of the I/O pins .............................................................................................. 52 Simulations and Waveforms............................................................................................ 53 Pseudocode ................................................................................................................... 54 ai_fork: Try to Make a Fork ................................................................................................. 56 Description .................................................................................................................... 56 Description of the I/O pins ............................................................................................... 57 Simulations and Waveforms........................................................................................... 58 Pseudocode ................................................................................................................... 59 ai_block_fork: Try to Block a Fork....................................................................................... 61 Description .................................................................................................................... 61 Description of the I/O pins .............................................................................................. 62 Simulations and Waveforms........................................................................................... 63 Pseudocode ................................................................................................................... 64 ai_direct_write: Other Strategies ........................................................................................67 Description .................................................................................................................... 68 Description of the I/O pins .............................................................................................. 68

iii

iv

Table of Contents Simulations and Waveforms........................................................................................... 69 Pseudocode .................................................................................................................... 70 ai_random: Very Dumb AI ................................................................................................... 71 Description ..................................................................................................................... 71 Description of the I/O pins ............................................................................................... 72 Simulations and Waveforms............................................................................................ 73 Pseudocode .................................................................................................................... 73 Proposed modifications to Design ..........................................................................................74 Problems encountered ........................................................................................................... 75 Conclusion ..............................................................................................................................76 Appendix ................................................................................................................................ 77 Appendix I: Global Architecture of the Game (ttt.vhd) ......................................................... 77 Appendix II: Finite State Machine ........................................................................................ 78

Table of Figures

TABLE OF FIGURES
Figure 1: Block Diagram of the Ai_Control Component ............................................................ 6 Figure 2: FSM DIAGRAM, LEVEL = 1 .........................................................................................7 Figure 3: FSM DIAGRAM, LEVEL = 2 ........................................................................................ 8 Figure 4: FSM DIAGRAM, LEVEL = 3 ........................................................................................ 9 Figure 5: FSM DIAGRAM, LEVEL = 4 .......................................................................................10 Figure 6: RTL diagram of the Game Controller ........................................................................ 13 Figure 7: Compilation Report for g27_control.vhd ...................................................................14 Figure 8: Timing Analysis Report for g27_control.vhd .............................................................14 Figure 9: Simulation of the controller ...................................................................................... 15 Figure 10: Simulation of the controller .................................................................................... 15 Figure 11: Simulation of the controller ................................................................................... 16 Figure 12: Simulation of the controller .................................................................................... 17 Figure 13: Simulation of the controller ....................................................................................18 Figure 14: Convention Used for the std_table Type ................................................................ 19 Figure 15: Table of all State Names Defined by the Custom_Types Package .......................... 20 Figure 16: Block Diagram of the ttt Component ..................................................................... 22 Figure 17: Input Pin Assignments for g27_ttt.vhd ................................................................... 22 Figure 18: Input Pin Assignments for g27_ttt.vhd ....................................................................23 Figure 19: Flow Summary for g27_ttt.vhd ...............................................................................23 Figure 20: Resource Utilisation by Entity ................................................................................ 24 Figure 21: Timing Analysis Summary ..................................................................................... 24 Figure 22: Overall Simulation of the Game ............................................................................. 25 Figure 23: Exceptional Case Handling in the Game ................................................................. 26 Figure 24: Block Diagram of the sys_led_state component .................................................... 28 Figure 25: Block Diagram of the gm_led Component .............................................................. 31 Figure 26: Bit Assignment for the Seven-Segment Display......................................................32 Figure 27: Initial Simulations of the gm_led Component .........................................................32 Figure 28: Simulation of the gm_led Component Through Multiple States.............................. 33

vi

Table of Figures Figure 29: Final Simulation of the gm_led Component Testing the Dimming ..........................34 Figure 30: Block Diagram of the ga_array component .............................................................38 Figure 31: Simulation Waveform for the ga_array Component ............................................... 39 Figure 32: Block Diagram of the ga_array_mux component ................................................... 42 Figure 33: Simulation Waveform for the ga_array_mux Component .......................................43 Figure 34: Block Diagram of the gm_status Component......................................................... 46 Figure 35: Simulation Waveform for the gm_status Component ............................................ 46 Figure 36: Block Diagram of the hp_wait Component ............................................................ 49 Figure 37: Simulation Waveform of the hp_wait component .................................................. 50 Figure 38: Block Diagram of the ai_find_win Component ........................................................ 53 Figure 39: Waveform of ai_find_win covering all possible winning rows .................................. 53 Figure 40: Waveform of ai_find_win covering all possible Losing rows ................................... 54 Figure 41: Waveform of ai_find_win covering Invalid Rows .................................................... 54 Figure 42: Block Diagram of the ai_fork Component .............................................................. 58 Figure 43: Waveform for ai_fork............................................................................................. 58 Figure 44: Block Diagram of the ai_block_fork Component .................................................... 63 Figure 45: First Simulation Waveform for the ai_block_fork Component ................................ 63 Figure 46: Second Simulation Waveform for the ai_block_fork Component ........................... 64 Figure 47: Third Simulation Waveform for the ai_block_fork Component .............................. 64 Figure 48: Block Diagram of the ai_direct_write Component ................................................. 69 Figure 49: Waveform of AI_direct_write for the first two substrategies ................................... 70 Figure 50: Waveform of AI_direct_write for the Last two substrategies................................... 70 Figure 51: Scanning order of the ai_random component ......................................................... 72 Figure 52: Block Diagram of the ai_random Component ......................................................... 73 Figure 53: Simulation Waveform for the ai_random component ............................................. 73 Figure 54: Block Diagram of the Game .................................................................................... 77 Figure 55: Finite State Machine diagram for the complete game ............................................. 78

Discussion of the design

INTRODUCTION
For the purpose of the laboratory component of the Digital System Design course, our team was tasked to develop a functioning Tic-Tac-Toe game implemented on the Altera Cyclone II board using the Altera Quartus II software. The 3x3 Tic-Tac-Toe table is stored in a 2-dimensional array holding 2-bit values. Instead of using Xs and Os as symbols, these symbols are given a numerical representation: an X mark is denoted by a 01 value while an O mark is denoted by a 11 value. The human player, or HP, always plays X marks, while the computer plays O marks only. In the final part of the lab, we had to integrate the final design of the game on the board. In order to achieve the complete functionality of the game, we have opted to construct the game using an entirely original design as we envisioned a more robust design structure than was recommended. This modular design helped not only debug the functions we wrote mush more easily, but it also provided us with greater control over each part of the design. We implemented the Furthermore, our AIs game playing strategy is very strong; much more so than in the recommended design in the lab. Finally, the AI can decide to play offensively or defensively depending on the situation, rather than make the decision based on user input. Hence, none of the designs we had created in the previous parts of the lab have been retained in our final design. In our design, we have notably implemented the following extra features: AI has multiple stages of difficulty ranging from dumb to exclusively offensive to exclusively defensive to expert A complete user interface utilizing the 4 seven-segment LED display, indicating the current state of the game table and outputting messages to the user (more about this later) Player can specify whether the human player or the AI would start the game

Discussion of the design First, we will discuss the controller element at the heart of the design for the 5th lab. Then, we will discuss the rest of the design, explaining what each component does each step of the way.

Discussion of the design

LAB 5: THE CONTROLLER AI_CONTROL

DISCUSSION OF THE DESIGN The controller functions as the state machine of the overall design. Based on the current state of the state machine and the input control signals, the state machine reaches a new state. The controller, at the rising edge of the clock, outputs the current state of the finite state machine to all the components that only function based on the current state. States described in the state machine: sg: Start Game (Initialize all components of the game). Whenever the reset button is pressed, this will become the new state. hp_move: Human players move (remain in this state until the human player makes a move, in which case the next state is the status state) status: Check whether the game has ended, either as a tie (next state becomes tie), a human player win (next state becomes loss). If the game is not over, change the state to the appropriate game strategy state dictated by the current difficulty level. States running the game strategy for the AI. If the computer makes a move, the next state becomes AI_Status. If not, the next state will depend on the current difficulty level set by the human player (more on this later). o ai_try_win: If there exists a row containing 2 O marks and an empty space, then AI places an O mark in the empty space to win the game. o ai_try_block: If there exists a row containing 2 X marks and an empty space, then AI places an O mark in the empty space to stop the HP from winning. o ai_try_fork: If there exists a move that creates a fork (two rows each containing a pair of O marks and a single empty space), then the AI will make that move. o ai_try_blockfork: If the HP is on the verge of creating a fork, make a move to block the creation of the fork. o ai_try_direct: Small sub strategy: If the centre cell of the game table is empty, the AI will play there

Description of the I/O pins If a corner cell is occupied by an X mark and if the opposite corner cell is empty, the AI will place an O mark in that empty cell. AI adds an O mark in the first free corner cell AI adds an O mark in the first free side cell.

o ai_random: This state is the only reached when the difficulty level user_levl is set to 0. AI scans through the table in order until an empty cell is detected. AI places an O mark in that cell ai_status: Similar to status, except that it checks whether the game has ended by a win by the AI (next state = Win) or by a tie (next state = Tie). If the human player can still play, the next state becomes HP_move. Loss: End State of Game caused by the loss of the AI. Game starts again upon the value of the Reset signal Tie: End State of Game caused by a tie game (the table is completely filled with O and X marks without either the HP or the AI winning). Game starts again upon the value of the Reset signal Win: End State of Game caused by the AI winning. Game starts again upon the value of the Reset signal DESCRIPTION OF THE I/O PINS Input Pins: reset: std_logic o User input o Indicates a request to reset the game clock: std_logic start: std_logic o User input o Required to start the actual game (i.e. allows the human player or the AI to make a move) hp_mm: std_logic

Description of the I/O pins o Outputted by the hp_wait component o Indicates that the human player has made a move ai_mm: std_logic o Outputted by each component dealing with the strategy of the AI o Indicates that the human player has made a move ai_loss: std_logic o Outputted by the gm_status component o Indicates that the game has ended and that the AI has lost ai_tie: std_logic o Outputted by the gm_status component o Indicates that the game has ended in a tie ai_win: std_logic o Outputted by the gm_status component o Indicates that the game has ended and that the AI has won level: std_logic_vector(1 downto 0) o User Input o Specifies the difficulty level of the AI hp_start: std_logic o User Input o Specifies whether the human player (when set to 0) or the AI (when set to 1) will start the game Output Pins: cur_state: state_type o A one-hot encoded value representing the current state of the finite state machine.

FSM schematics

FIGURE 1: BLOCK DIAGRAM OF THE AI_CONTROL COMPONENT

FSM SCHEMATICS Unfortunately, due to the complex behaviour of our state machine, it is be preferable to decompose the state machine based on the value of the difficulty level signal level, yielding four different state machine diagrams. For the sake of completeness, a complete finite state machine diagram generated by the Quartus II software is included in the appendix (see Figure 55: Finite State Machine diagram for the complete game for more details). Please note that in all cases, the next state will remain the current state if the transition conditions specified in the diagrams below are not met.

FSM schematics

FIGURE 2: FSM DIAGRAM, LEVEL = 1

FSM schematics

FIGURE 3: FSM DIAGRAM, LEVEL = 2

FSM schematics

FIGURE 4: FSM DIAGRAM, LEVEL = 3

10

FSM schematics

FIGURE 5: FSM DIAGRAM, LEVEL = 4

Pseudocode PSEUDOCODE
if (clock='1' and clock'event){ if (reset='0'){ fstate = sg; else fstate = reg_fstate; } } reg_cur_state = sg; cur_state = sg; switch(fstate){ case sg: if ((start == '0') and hp_start == '0'){ reg_fstate = hp_move; } else if ((start == '0') and hp_start == '1'){ reg_fstate = status; else reg_fstate = sg; } reg_cur_state = sg; case hp_move: if (hp_mm == '1'){ reg_fstate = status; else reg_fstate = hp_move; } reg_cur_state = hp_move; case status: if ai_loss == '1'{ reg_fstate = loss; } else if ai_tie == '1'{ reg_fstate = tie; } else if ai_win == '1'{ reg_fstate = win; } else if ((((not((level(1) == '1')) and not((level(0) == '1'))) and not((ai_tie == '1'))) and not((ai_loss == '1')))){ reg_fstate = ai_random; } else if (((((level(1) == '1') and not((level(0) == '1'))) and not((ai_tie == '1'))) and not((ai_loss == '1')))){ reg_fstate = ai_try_block; else reg_fstate = ai_try_win; } reg_cur_state = status; case ai_random: if (ai_mm == '1'){ reg_fstate = ai_status; else

11

12

Pseudocode
reg_fstate = ai_random; } reg_cur_state = ai_random; case ai_try_win: if (ai_mm == '1'){ reg_fstate = ai_status; } else if (((not((level(1) == '1')) and (level(0) == '1')) and not((ai_win == '1')))){ reg_fstate = ai_try_fork; else reg_fstate = ai_try_block; } reg_cur_state = ai_try_win; case ai_try_fork: if ((ai_mm == '1')){ reg_fstate = ai_status; } else if (((not((level(1) == '1')) and (level(0) == '1')) and not((ai_mm == '1')))){ reg_fstate = ai_try_direct; else reg_fstate = ai_try_blockfork; } reg_cur_state = ai_try_fork; case ai_try_block: if ((ai_mm == '1')){ reg_fstate = ai_status; } else if ((((level(1) == '1') and not((level(0) == '1'))) and not((ai_mm == '1')))){ reg_fstate = ai_try_blockfork; else reg_fstate = ai_try_fork; } reg_cur_state = ai_try_block; case ai_try_blockfork: if ((ai_mm == '1')){ reg_fstate = ai_status; else reg_fstate = ai_try_direct; } reg_cur_state = ai_try_blockfork; case ai_try_direct: if ((ai_mm == '1')){ reg_fstate = ai_status; else reg_fstate = ai_try_direct; } reg_cur_state = ai_try_direct; case ai_status: if (ai_loss == '1'){

Other figures
reg_fstate = loss; } else if (ai_tie == '1'){ reg_fstate = tie; } else if (ai_win == '1'){ reg_fstate = win; else reg_fstate = hp_move; } reg_cur_state = ai_status; case loss: reg_fstate = loss; reg_cur_state = loss; case tie: reg_fstate = tie; reg_cur_state = tie; case win: reg_fstate = win; reg_cur_state = win; case others: reg_cur_state = sg; } cur_state = reg_cur_state;

13

OTHER FIGURES Heres the RTL diagram of the controller. The yellow box represents the finite state machine. As shown previously, the controller simply encapsulates the finite state machine.
fstate
sg

ai_loss ai_mm ai_tie ai_win clock hp_mm hp_start reset start level[1..0]

hp_move ai_loss status ai_mm ai_try_win ai_tie ai_try_block ai_win ai_try_fork clk ai_try_blockfork hp_mm ai_try_direct hp_start ai_random reset ai_status start loss level[1..0] tie win

cur_state.sg cur_state.hp_move cur_state.status cur_state.ai_try_win cur_state.ai_try_block cur_state.ai_try_fork cur_state.ai_try_blockfork cur_state.ai_try_direct cur_state.ai_random cur_state.ai_status cur_state.loss cur_state.tie cur_state.win

FIGURE 6: RTL DIAGRAM OF THE GAME CONTROLLER

14

Reports REPORTS
g27_control.vhd Total logic elements 30 / 18,752 ( < 1 % ) Total combinational functions 30 / 18,752 ( < 1 % ) Dedicated logic registers 13 / 18,752 ( < 1 % ) Total registers 13 Total pins 24 / 315 ( 8 % )
FIGURE 7: COMPILATION REPORT FOR G27_CONTROL.VHD

Type Worstcase tsu Worstcase tco Worstcase th

Actual Time 5.648 ns 7.344 ns 0.085 ns

From reset fstate.status level[0]

To fstate.ai_random cur_state.status fstate.ai_try_win

FIGURE 8: TIMING ANALYSIS REPORT FOR G27_CONTROL.VHD

CONTROLLER IMPLEMENTATION To test the functionality of the controller on the Altera board, we made use of the red and LEDs as outputs displaying the current state that the controller is in. We also used the toggle switches as the input signals of the state machine. A push button was used as a manual clock to allow for precise testing on the board. Described below are the pins we assigned during the implementation of the game controller. INPUT PINS: Signal start hp_start hp_mm ai_mm ai_loss ai_tie ai_win clock Name on Pin board Location SW8 PIN_M1 SW7 PIN_M2 SW6 PIN_U11 SW5 PIN_U12 SW4 PIN_W12 SW3 PIN_V12 SW2 PIN_M22 KEY0 PIN_R22

Simulation waveforms level[1] level[0] SW1 SW0 PIN_L21 PIN_L22

15

FIGURE 9: SIMULATION OF THE CONTROLLER

OUTPUT PINS: Signal cur_state.sg cur_state.hp_move cur_state.status cur_state.ai_try_win cur_state.ai_try_block cur_state.ai_try_fork cur_state.ai_try_blockfork cur_state.ai_try_direct cur_state.ai_random cur_state.ai_status cur_state.loss cur_state.tie cur_state.win Name on board LEDR9 LEDR8 LEDR7 LEDR6 LEDR5 LEDR4 LEDR3 LEDR2 LEDR1 LEDR0 LEDG7 LEDG6 LEDG5 Pin Location PIN_R17 PIN_R18 PIN_U18 PIN_Y18 PIN_V19 PIN_T18 PIN_Y19 PIN_U19 PIN_R19 PIN_R20 PIN_Y21 PIN_Y22 PIN_W21

FIGURE 10: SIMULATION OF THE CONTROLLER

SIMULATION WAVEFORMS Before testing the design on the board, we simulated the behaviour of the finite state machine. First, we tested the flow of the controller for each level of difficulty specified by the input signal level, as well as the behaviour at the beginning of the game (when the reset and start signals need to be set to 0 before the human player can start playing). As long as ai_mm is not set, the controller should go through all possible strategy states. In the waveform below, we observe that indeed, the game does not start until the user presses the start button (i.e. start = 0). Also, we see that the strategy states chosen by the controller for each possible value of level follow exactly what the state diagrams indicated, that is, we have the following state flows for level = 0: status => ai_random

16

Simulation waveforms level = 1: level = 2: level = 3: status => ai_try_win => ai_try_fork => ai_try_direct => ai_random status => ai_try_block => ai_try_block_fork => ai_try_direct status => ai_try_win => ai_try_block => ai_try_fork => ai_try_block_fork

=> ai_try_direct Furthermore, in this simulation, we also observe that the states ai_try_direct and hp_move wait until input signals hp_mm or ai_mm are asserted.

FIGURE 11: SIMULATION OF THE CONTROLLER

Our next test for the controller checks if the controller can react to a move being made by a strategy state (i.e. ai_mm = 1). To perform this test, we modified the previous simulation by specifying the difficulty level to 11, enabling all the strategy states. Furthermore, we conducted multiple tests where the ai_mm signal changes at a different time, making sure that the next state following any move by the AI (ai_mm = 1) is the ai_status state (the state that check the current status of the game). The waveform below confirms that the component behaves as expected.

Simulation waveforms

17

FIGURE 12: SIMULATION OF THE CONTROLLER

Our last simulation tests all end game scenarios, as well as the functionality of the hp_start input signal, which indicates whether or not the AI starts the game and is checked when the start input signal is set to 0. An end game scenario (either AI win, HP win or tie) is outputted by the gm_status component, which is only active during the status (after the human player made a move) and ai_status (after the AI made a move) states. We simulate the six final outcomes where any of the three game termination input signals (caused by an AI win, a HP win or a tie game) are asserted during either the status or ai_status states The waveform below is the result of these tests. As we can see, the controller is not affected by the game termination signal until the current state is one of the two listed above. In addition, when one of the game termination input signals (ai_loss, ai_tie and ai_win) is set to 1, the appropriate next state (loss, tie, win) is reached.

18

Simulation waveforms

FIGURE 13: SIMULATION OF THE CONTROLLER

Also, we verify the functionality of the hp_start input by asserting the signal in the last three tests (in which we focus on the behaviour of the controller after the AI makes a move). As seen below, the AI makes its move right after the game starts. Even though not all possible inputs were simulated for this component, the VHDL code for the controller is so repetitive (i.e. the code follows a very straight-forward structure) that we expect the whole controller to function correctly after the simulations above. Finally, while these simulations involve inputs that are active only during a clock cycle, testing the controller on the board in real time showed that controller works even if an input is held over multiple clock cycles. Therefore, we can confidently say that the controller works completely as intended.

DISCUSSION OF OVERALL DESIGN

custom_types: Custom Data Types Our design of the Tic-Tac-Toe game is segmented into many simple components that are mutually connected to one another. All components are synchronised by a common clock, either directly (the clock is an input of the component) or indirectly (the clock changes the value of the main input signal of the component. Here is a list of all the components we used in our final design (ttt.vhd): control Game Controller sys_led_state LED Current State Display gm_led Seven segment LED Display ga_array Game Table ga_array_mux Multiplexer for the Game Table gm_status Game Status Check hp_wait Wait for HP move ai_find_win Try to Win ai_fork Try to Create a Fork ai_block_fork Try to Block a Fork ai_direct_write Other Strategies ai_random Very Dumb AI

19

CUSTOM_TYPES: CUSTOM DATA TYPES Although not a component, the custom_types.vhd file contains a package that defines the two custom data types used by all components in our global design. These are: std_table: array (0 to 2, 0 to 2) of std_logic_vector(1 downto 0) o 2-dimensional representation of the game table ga_array

FIGURE 14: CONVENTION USED FOR THE STD_TABLE TYPE

state_type: User-defined generic type o Defines the state names used for the finite state machine.

20

ttt: Overall Design sg hp_move status ai_try_win ai_try_block ai_try_fork ai_try_blockfork ai_try_direct ai_random ai_status loss tie win

FIGURE 15: TABLE OF ALL STATE NAMES DEFINED BY THE CUSTOM_TYPES PACKAGE

TTT: OVERALL DESIGN DESCRIPTION This circuit is the top level entity of the project. It basically groups all components of the circuit in a single vhdl file and manages the inputs and outputs with the outside world. By observing the code, one can see that the circuit basically assigns the correct signal lines to each component of the tic-tac-toe game. The input signals (in the entity) are those which the user can directly control using the Altera Board (Except clk). The outputs hex0_out, hex1_out, hex2_out and hex3_out are information for LED display and the signal state_out is information for the red light led displays. The components g27_control, g27_ga_array, g27_gm_led, g27_hp_wait, g27_sys_led_state all follow the clock; the components g27_ga_array_mux, g27_gm_status, g27_ai_find_win, g27_ai_fork, g27_ai_block_fork, g27_ai_direct_write, g27_ai_random are dependent only on changes of curr_state signal. Note: though some components do not follow the clock explicitly, the current state does depend on the clock. DESCRIPTION OF THE I/O PINS The ai_fork component has exactly the same pin configuration as the ai_try_win component (as do all the components handling the AI strategy). Input Pins: clk: std_logic

ttt: Overall Design o Clock of the circuit reset: std_logic o if high, nothing | if low, clear all signals and GA array user_start: std_logic o if low, HP starts game | if high, AI starts game user_row: std_logic_vector(1 downto 0) o row coordinate inputted by HP] user_col: std_logic_vector(1 downto 0) o col coordinate inputted by HP user_set: std_logic o when low, game assumes HP coordinates are ready o when high, the circuit hold its state user_toggle_marks: std_logic o if low, show O marks | if high, show X marks user_show_array: std_logic o Force the GA array to be displayed on LED no matter the state user_lvl: std_logic_vector(1 downto 0) o level of difficulties: 00=1, 01=2, 10=3, 11=4 user_begin: std_logic o if low, user acknowledges the beginning of the game

21

Output Pins: hex0_out: std_logic_vector(0 to 6) o 2nd rightmost LED unit hex2_out: std_logic_vector(0 to 6 o 2nd leftmost LED unit hex3_out: std_logic_vector(0 to 6) o leftmost LED unit state_out: std_logic_vector(9 downto 0) o red light LED display

22

ttt: Overall Design

FIGURE 16: BLOCK DIAGRAM OF THE TTT COMPONENT

PIN ASSIGNMENTS Input pins:


Signal clk reset user_begin user_col[1] user_col[0] user_lvl[1] user_lvl[0] user_row[1] user_row[0] user_set user_show_array user_start user_toggle_marks Assigned Pin PIN_D12 PIN_T21 PIN_M22 PIN_M2 PIN_U11 PIN_L21 PIN_L22 PIN_L2 PIN_M1 PIN_R21 PIN_R22 PIN_T22 PIN_U12

FIGURE 17: INPUT PIN ASSIGNMENTS FOR G27_TTT.VHD

Output pins:
Signal hex0_out[0] hex0_out[1] hex0_out[2] hex0_out[3] Assigned Pin PIN_J2 PIN_J1 PIN_H2 PIN_H1 Signal hex0_out[4] hex0_out[5] hex0_out[6] hex1_out[0] Assigned Pin PIN_F2 PIN_F1 PIN_E2 PIN_E1

ttt: Overall Design


Signal hex1_out[1] hex1_out[2] hex1_out[3] hex1_out[4] hex1_out[5] hex1_out[6] hex2_out[0] hex2_out[1] hex2_out[2] hex2_out[3] hex2_out[4] hex2_out[5] hex2_out[6] hex3_out[0] hex3_out[1] hex3_out[2] Assigned Pin PIN_H6 PIN_H5 PIN_H4 PIN_G3 PIN_D2 PIN_D1 PIN_G5 PIN_G6 PIN_C2 PIN_C1 PIN_E3 PIN_E4 PIN_D3 PIN_F4 PIN_D5 PIN_D6 Signal hex3_out[3] hex3_out[4] hex3_out[5] hex3_out[6] state_out[9] state_out[8] state_out[7] state_out[6] state_out[5] state_out[4] state_out[3] state_out[2] state_out[1] state_out[0] Assigned Pin PIN_J4 PIN_L8 PIN_F3 PIN_D4 PIN_R17 PIN_R18 PIN_U18 PIN_Y18 PIN_V19 PIN_T18 PIN_Y19 PIN_U19 PIN_R19 PIN_R20

23

FIGURE 18: INPUT PIN ASSIGNMENTS FOR G27_TTT.VHD

REPORTS
g27_control.vhd Total logic elements 508 / 18,752 ( 3 % ) Total combinational functions 498 / 18,752 ( 3 % ) Dedicated logic registers 97 / 18,752 ( < 1 % ) Total registers 97 Total pins 51 / 315 ( 16 % )
FIGURE 19: FLOW SUMMARY FOR G27_TTT.VHD

Component |g27_ai_block_fork:ai_block_fork| |g27_ai_direct_write:ai_direct_write| |g27_ai_find_win:ai_find_win| |g27_ai_fork:ai_fork| |g27_ai_random:ai_random| |g27_control:control| |g27_ga_array:ga_array| |g27_ga_array_mux:ga_array_mux| |g27_gm_led:gm_led| |g27_gm_status:gm_status| |g27_hp_wait:hp_wait|

LC Combinationals 38 8 53 98 7 25 29 80 83 50 19

LC Registers 0 0 0 0 0 13 18 0 51 0 5

24

ttt: Overall Design


|g27_sys_led_state:sys_led_state| Total (g27_ttt) 8 498 10 97

FIGURE 20: RESOURCE UTILISATION BY ENTITY

Type Worst-case tsu Worst-case tco Worst-case th Clock Setup: 'clk' Clock Hold: 'clk'

Slack

Time 8.021 ns 11.521 ns 0.494 ns

From user_show_array g27_gm_led:gm_led|hex3_o ut[6] user_lvl[1] g27_ga_array:ga_array|ga_ar ray[1][2][1] g27_control:control|fstate.ti e

To g27_gm_led:gm_led|hex2_o ut[2] hex3_out[6] g27_gm_led:gm_led|hex0_o ut[5] g27_ga_array:ga_array|ga_ar ray[0][1][0] g27_control:control|fstate.ti e

29.169 ns 0.445 ns

80.02 MHz ( period = 12.497 ns ) N/A

FIGURE 21: TIMING ANALYSIS SUMMARY

SIMULATIONS AND WAVEFORMS Since describing all the control lines is excessively troublesome, we will only demonstrate the functionality of this circuit by performing a simulation of a game. To allow better understanding of the output signals, here is a list of states associated with their corresponding state_out value: sg => hp_move => status => ai_try_win => ai_try_block => ai_try_fork => ai_try_blockfork => ai_try_direct => loss => win => tie => ai_random => "1000000000"; "0100000000"; "0010000000"; "0001000000"; "0000100000"; "0000010000"; "0000001000"; "0000000100"; "0000000010"; "0000000001"; "0000000011"; "0001111100";

ttt: Overall Design ai_status => "1110000000";

25

The following is a waveform of the start of the game, which was used during the demo. The full game is presented in the waveform file g27_ttt.vwf.

FIGURE 22: OVERALL SIMULATION OF THE GAME

Note that the input signals are active low. We chose level 4 (user_lvl = 11 = 3), which means the AI will toggle between offense and defense accordingly while being able to deal with and perform forks. Notice that the output state_out always follow the present state of the game, and if compared to the list presented above, one should see that they are correct. The LED display are explained in g27_gm_led.vhd section, with the left most LED unit corresponding to the value of hex3_out output. hex1_out, hex2_out and hex3_out are used to display the game using their horizontal bars (which coincidentally have a 3 by 3 mapping, perfect for displaying the game.) hex4_out is used to display which of the two marks (X or O) is currently displayed, which is determined by the signal user_toggle_marks (if high, mark = X | if low, mark = O). The toggle mark function is demonstrated at the end of the first cycle of the game, where the LED display suddenly changed to display the only X mark on the field when the signal user_toggle_marks is set to high. After the first cycle through the states, the

26

ttt: Overall Design AI added its move to the game board, which is displayed on the LED at the correct position, and the circuit is on hold until the next cycle begins when the player inserts his/her next mark. The end result of the game is in the state tie. This simulation simply shows that the circuit does perform the tic-tac-toe game provided that the user continuously feed legal coordinates for the X marks.

FIGURE 23: EXCEPTIONAL CASE HANDLING IN THE GAME

In the second waveform (g27_ttt2.vwf), notice that the circuit will be stuck at the state hp_move as long as the r_c signal gives an illegal coordinate where there already was an inserted mark. In the first case, the coordinate is in the center of the field, where the HP already inserted an X mark. In the second case, the coordinate correspond to the lower left corner cell, where the AI has already inserted an O mark. After that, the signal reset has

sys_led_state: LED Current State Display been set to low, thus clearing the entire GA array, allowing the states to flow until the circuit attempt to insert a mark at an already taken position. The circuit is put on hold afterward.

27

SYS_LED_STATE: LED CURRENT STATE DISPLAY DESCRIPTION This circuits job is to send out the right signal for the red led lights to represent the right current state in the FSM. By looking at which red LED light is on and off, the user can determine the present state of the game. This is a component we used to debug the finite state machine and decided to keep in our overall design. The circuit simply looks at the current state at every clock cycle and outputs the state represented by the LED lights. Each state is assigned a unique pattern by which the 9 red led lights will shine to indicate them. The reader can easily see this by directly looking at the code. DESCRIPTION OF THE I/O PINS Input Pins: clk: std_logic o The clock, generated by the 24MHz Oscillator on the Altera board curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state Output Pins: state_out: std_logic_vector(9 downto 0) o Outputted towards the boards pins o Binary representation for the red led lights

28

sys_led_state: LED Current State Display

FIGURE 24: BLOCK DIAGRAM OF THE SYS_LED_STATE COMPONENT

SIMULATIONS AND WAVEFORMS The following waveform (g27_sys_led_state1) demonstrates the output state_out which has a different binary number for each possible state.

PSEUDOCODE
if(rising_edge(clk)){ switch(curr_state){ case sg: state_out = "1000000000"; case hp_move: state_out = "0100000000"; case status: state_out = "0010000000"; case ai_try_win: state_out = "0001000000"; case ai_try_block: state_out = "0000100000"; case ai_try_fork: state_out = "0000010000"; case ai_try_blockfork: state_out = "0000001000"; case ai_try_direct: state_out = "0000000100"; case loss: state_out = "0000000010"; case win: state_out = "0000000001";

sys_led_state: LED Current State Display


case tie: state_out = "0000000011"; case ai_random: state_out = "0001111100"; case ai_status: state_out = "1110000000"; } }

29

GM_LED: SEVEN SEGMENT LED DISPLAY DESCRIPTION This circuits job is to display the tic-tac-toe 3 by 3 board, the statuses of the game, the level of difficulty and the whereabouts of the HP next move. The game itself is displayed using the horizontal bars of the LEDs since we have 3 bars per column and we have 4 bars width, which is sufficient to display the game. The information displayed on the LED depends on the current state of the game (curr_state). For example, if curr_state = win, the LED will display the word FAIL to indicate that the AI won the game. There is a set of priority on the states to determine the message from to which state will be displayed , and is in the following descending order: win, loss, tie, sg, and the rest are equivalent (except hp_move). When the current state is loss, the word win will be displayed. When the current state is tie, the word tie will be displayed. When the current state is sg, the symbol lvl will be displayed on the left followed by an integer of 1 to 4 depending on the signal level (level: 00 = 1, 01= 2, 10=3, 11=4). When the current state is anything else, the LED display the position of all target marks on the field (X if mark_x_en = 01, O if mark_x_en = 11) When the current state is hp_move, the LED will not only perform the task described in the bullet right above, but will also display the current position the HP is about to insert his/her next mark (user_curr_row, user_curr_col). Note that this position will

30

sys_led_state: LED Current State Display be displayed as a dim light bar (LED used only once per 15 clock cycle) no matter the value of mark_x_en. To finish, when the signal show_array_n = 0, the circuit will bypass the states win, loss, tie and sg and directly show the present situation of the game by displaying the marks. This allows the HP to view the game board even if the current state is win, loss, or tie. DESCRIPTION OF THE I/O PINS Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks show_array_n: std_logic o User input o This signal forces the LED to display the GA array when low mark_x_en: std_logic o User input o If signal = 11, only O marks will be displayed; if signal = 01, then only X marks will be displayed. level: std_logic_vector(1 downto 0) o User input o The level of AI difficulty from 1 to 4 (00 to 11) user_curr_row: in std_logic_vector(1 downto 0)

sys_led_state: LED Current State Display o User input o The row coordinate the HP is currently selecting user_curr_col: in std_logic_vector(1 downto 0)) o User input o The column coordinate the HP is currently selecting Output Pins: hex0_out: std_logic_vector(0 to 6) hex1_out: std_logic_vector(0 to 6) hex2_out: std_logic_vector(0 to 6) hex3_out: std_logic_vector(0 to 6) o All output to the board through the seven segment display. o 7-bit representation of each of the four hex-displays on the Altera board

31

FIGURE 25: BLOCK DIAGRAM OF THE GM_LED COMPONENT

SIMULATIONS AND WAVEFORMS The following waveform (g27_gm_led1) demonstrate the cases for the states win, loss, tie and sg. On the image presented on top of the waveform is an index map of a single LED display unit to the 7 bits of the hex_out output, from 6 at left to 0 on the right. Using the output of the waveform, one can determine that the outputs correspond to the correct result for each state.

32

sys_led_state: LED Current State Display

FIGURE 26: BIT ASSIGNMENT FOR THE SEVEN-SEGMENT DISPLAY

FIGURE 27: INITIAL SIMULATIONS OF THE GM_LED COMPONENT

The next waveform (g27_gm_led2) tests the case when the remaining states. If one maps the outputs correctly, it can be seen that the outputs represent the positions of the marks of the selected type (depending on mark_x_en).

sys_led_state: LED Current State Display

33

FIGURE 28: SIMULATION OF THE GM_LED COMPONENT THROUGH MULTIPLE STATES

The third waveform (g27_gm_led3) simply shows the state of hp_move where the LED only display once per 15 clock cycles.

34

sys_led_state: LED Current State Display

FIGURE 29: FINAL SIMULATION OF THE GM_LED COMPONENT TESTING THE DIMMING

PSEUDOCODE
if(rising_edge(CLK)){ hex0= hex1= hex2= hex3= "1111111"; "1111111"; "1111111"; "1111111";

// TODO: Change all inputs to 1 when done testing if(curr_state == win and show_array_n == '1'){ // Spell: Fail hex3= "0111000"; hex2= "0001000"; hex1= "1001111"; hex0= "1110001"; } else if(curr_state == loss and show_array_n == '1'){ // Spell: |_._| | .-. hex3= "1100001"; hex2= "1000011"; hex1= "1001111";

sys_led_state: LED Current State Display


hex0= "1101010"; } else if(curr_state == tie and show_array_n == '1'){ // Spell: Tie hex3= "1110000"; hex2= "1001111"; hex1= "0110000"; } else if(curr_state == sg){ // Spell: lvl # hex3= "1110001"; hex2= "1100011"; hex1= "1110001"; switch(level){ case "00" : hex0 = "1001111"; case "01" : hex0 = "0010010"; case "10" : hex0 = "0000110"; case default : hex0 = "1001100"; } }else { // In HEX0, show "X" if the user_toggle_marks switch // Also, set mark to "01" so that only "X" marks are case if(mark_x_en == '1'){ mark= "01"; hex0= "1001000"; }else { // In HEX0, show "O" if the user_toggle_marks switch // Also, set mark to "11" so that only "O" marks are case mark= "11"; hex0= "0000001"; }

35

is high shown when this is the

is low shown when this is the

// If the content of a cell in the game array is the current MARK (as specified above) //{ light up the corresponding LED on the hex display if(ga_array_in(0,0) == mark or (user_curr_row == "00" and user_curr_col == "00" and light == '1'))then hex3(3)= '0'; } if(ga_array_in(1,0) == mark or (user_curr_row == "01" and user_curr_col == "00" and light == '1')){ hex3(6)= '0'; } if(ga_array_in(2,0) == mark or (user_curr_row == "10" and user_curr_col == "00" and light == '1'))then hex3(0)= '0'; } if(ga_array_in(0,1) == mark or (user_curr_row == "00" and user_curr_col == "01" and light == '1'))then hex2(3)= '0'; }

36

ga_array: Game Table

if(ga_array_in(1,1) == mark or (user_curr_row == "01" and user_curr_col == "01" and light == '1'))then hex2(6)= '0'; } if(ga_array_in(2,1) == mark or (user_curr_row == "10" and user_curr_col == "01" and light == '1')){ hex2(0)= '0'; } if(ga_array_in(0,2) == mark or (user_curr_row == "00" and user_curr_col == "10" and light == '1'))then hex1(3)= '0'; } if(ga_array_in(1,2) == mark or (user_curr_row == "01" and user_curr_col == "10" and light == '1'))then hex1(6)= '0'; } if(ga_array_in(2,2) == mark or (user_curr_row == "10" and user_curr_col == "10" and light == '1'))then hex1(0)= '0'; } } if(curr_state == hp_move){ if(count == 15){ count = 0; light = '1'; }else { count = count + 1; light = '0'; } }else { light = '0'; } // Pass final values of the hex display to the output hex0_out = hex0; hex1_out = hex1; hex2_out = hex2; hex3_out = hex3; }

GA_ARRAY: GAME TABLE DESCRIPTION

ga_array: Game Table The GA array circuits job is simply updating any inputs from either the AI player or HP when called upon (write_en = 1) into the GA array. It also will clear the entire game board when necessary (when game starts or resets). This circuit activates only on the rising edges of the clock. In each clock cycle, it keeps track of the signals clear and write_en. When clear = 1, the GAs cells will all be emptied (reset). When write_en = 1, the circuit will insert the desired mark according to the value of the signal mark_type (10 = X, 11 = O) at the coordinates given by row_id and col_id. No matter what happens, the GA array will be updated through the output signal ga_array_out at each clock cycle (even if no change was made in the table). Note: it is assumed that the coordinates provided by row_id and col_id would lead to a free cell where a new mark can be inserted without erasing any previous inhabitant. These two signals are verified beforehand by other components. DESCRIPTION OF THE I/O PINS Input Pins: clk: std_logic o The clock, generated by the 24MHz Oscillator on the Altera board clear: std_logic o Generated by the ga_array_mux component o if clear = 1, GA array is emptied, i.e. all its contents are set to 00 write_en: std_logic o Generated by the ga_array_mux component o if write_en = 1, the a mark of type mark_type will be inserted at coordinate row_id, col_id mark_type: std_logic_vector(1 downto 0) o Generated by the ga_array_mux component

37

38

ga_array: Game Table o 11 = O mark; 10 = X mark row_id: integer range 0 to 2 o Generated by the ga_array_mux component o The row index of the newly inserted X mark col_id: integer range 0 to 2 o Generated by the ga_array_mux component o The column index of the newly inserted X mark Output Pins: ga_array_out: std_table o Outputted to all components o Representation of the current state of the game table

FIGURE 30: BLOCK DIAGRAM OF THE GA_ARRAY COMPONENT

SIMULATIONS AND WAVEFORMS The waveform presented below shows some cases of inserted marks, with the clear=1 case in the middle. As was assumed, the circuit fill out the array at the correct coordinates presented by row_id and col_id with the correct mark_type. The circuit also clears the entire array when clear=1.

ga_array_mux: Multiplexer for the Game Table

39

FIGURE 31: SIMULATION WAVEFORM FOR THE GA_ARRAY COMPONENT

PSEUDOCODE
if(rising_edge(CLK)){ if (clear = 1) { ga_array = ((0,0,0),(0,0,0),(0,0,0)); } if (write_en = 1){ ga_array(row_id, col_id) = mark_type; } }

GA_ARRAY_MUX: MULTIPLEXER FOR THE GAME TABLE DESCRIPTION This circuits job is assigning the correct command signals and correct mark to the right coordinates of the next cell to be filled depending on the present state. This circuit will check the present state at each clock cycle and send the appropriate signals to the ga_array circuit. Depending on the value of the signal curr_state, this circuit decides whether to clear the GA array (clear_out) or to insert a mark (mark_type_out) when needed (enable_out) at the correct coordinates in the GA array (col_id & row_id). The output signal mark_type_out depends if the current state belong to the HP or AI (mark = 11 if AI, mark = 10 if HP). If the current state is hp_move and the HP has made his move (hp_mademove = 1), the row_id and col_id will correspond to the coordinates inputted by HP. If the current state belongs to one of the AI states (ai_try_win, ai_try_block, ai_try_fork, ai_try_blockfork, ai_try_direct, ai_random), the circuit must choose between various coordinate input signal lines depending on which of the various AI states the present state is. As an example, if the curr_state = ai_try_fork, the circuit will pass the ga_array component the coordinates generated by the component ai_fork (row_id_ai_fork, col_id_ai_fork) and the enable signal ai_fork_en. If

40

ga_array_mux: Multiplexer for the Game Table curr_state = sg, the clear signal will be sent to ga_array component and the GA array will be cleared. DESCRIPTION OF THE I/O PINS Input Pins: clk: std_logic o The clock, generated by the 24MHz Oscillator on the Altera board curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state row_id_hp_wait : integer range 0 to 2 o row coordinate generated by the hp_wait component col_id_hp_wait : integer range 0 to 2 o column coordinate generated by HP] hp_mademove : std_logic o enable write_en of ga_array component only when the HP made a move

row_id_ai_find_win : integer range 0 to 2 o row coordinate generated by ai_find_win component col_id_ai_find_win : integer range 0 to 2 o col coordinate generated by ai_find_win component ai_find_win_en : std_logic o enable signal generated by ai_find_win component

row_id_ai_fork : integer range 0 to 2

ga_array_mux: Multiplexer for the Game Table o row coordinate generated by ai_fork component col_id_ai_fork : integer range 0 to 2 o col coordinate generated by ai_fork component ai_fork_en : std_logic o enable signal generated by ai_fork component

41

row_id_ai_blockfork : integer range 0 to 2 o row coordinate generated by ai_block_fork component col_id_ai_blockfork : integer range 0 to 2 o col coordinate generated by ai_block_fork component ai_blockfork_en : std_logic o enable signal generated by ai_block_fork component

row_id_ai_direct_write : integer range 0 to 2 o row coordinate generated by ai_random component col_id_ai_direct_write : integer range 0 to 2 o col coordinate generated by ai_random component ai_direct_write_en : std_logic o enable signal generated by ai_random component mark_type: std_logic_vector(1 downto 0) o Generated by the ga_array_mux component o 11 = O mark; 10 = X mark

Output Pins: row_id: integer range 0 to 2 o The row index of the newly inserted X mark col_id: integer range 0 to 2 o The column index of the newly inserted X mark

42

ga_array_mux: Multiplexer for the Game Table enable_out: std_logic o outputted enable signal clear_out: std_logic o outputted clear signal mark_type_out: std_logic_vector(1 downto 0) o outputted mark type

FIGURE 32: BLOCK DIAGRAM OF THE GA_ARRAY_MUX COMPONENT

SIMULATIONS AND WAVEFORMS The following waveform present cases of each state curr_state and the various consequences on the signals generated. The coordinates from each AI components are different from each other, thus allowing them to be identified at the output signals. By observing the waveform, one can determine that during the clock cycle of each state, their corresponding value of coordinates passed on to the output signals. The mark_type_out is 1 when in HP state and 3

ga_array_mux: Multiplexer for the Game Table when in AI states. The enable_out signal is high when in HP and AI states and only drops at sg state where the signal clear_out = 1. Note that the state ai_random is equivalent to ai_try_direct, and is not considered.

43

FIGURE 33: SIMULATION WAVEFORM FOR THE GA_ARRAY_MUX COMPONENT

PSEUDOCODE
if( hp_move ){

44

gm_status: Check Current Game Status


row_id = row_id_hp_wait; col_id = col_id_hp_wait; enable = hp_mademove; clear = 0; mark_type = 01; }if( ai_try_win ){ row_id = row_id_ai_find_win; col_id = col_id_ai_find_win; enable = ai_find_win_en; clear = 0; mark_type = 11; }if( ai_try_block ){ row_id = row_id_ai_find_win; col_id = col_id_ai_find_win; enable = ai_find_win_en; clear = 0; mark_type = 11; }if( ai_try_fork ){ row_id = row_id_ai_fork; col_id = col_id_ai_fork; enable = ai_fork_en; clear = 0; mark_type = 11; }if( ai_try_blockfork ){ row_id = row_id_ai_blockfork; col_id = col_id_ai_blockfork; enable = ai_blockfork_en; clear = 0; mark_type = 11; }if( ai_try_direct ){ row_id = row_id_ai_direct_write; col_id = col_id_ai_direct_write; enable = ai_direct_write_en; clear = 0; mark_type = 11; }if( sg ){ row_id = 0; col_id = 0; enable = 0; clear = '1'; mark_type = 11; }if( others ){ row_id = 0; col_id = 0; enable = 0; clear = 0; }

GM_STATUS: CHECK CURRENT GAME STATUS DESCRIPTION

gm_status: Check Current Game Status This circuits job is to determine the current status of the game, that is, whether the game has ended in a win, loss or a tie, or the game is still not complete. Status determination only occurs at state status or ai_status. To determine a win (for the AI), the circuit simply checks all possible row combinations in the game to see if a row is filled by O or X marks (if curr_state = status, X marks will be checked, otherwise, O marks will be checked). A tie occurs when no such row has been detected and there is no empty cell left in GA array. If no row is complete and free cells are still available, the game is incomplete (all 3 outputs low). DESCRIPTION OF THE I/O PINS Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks Output Pins: gm_loss: std_logic o Outputted towards the controller control o High if AI loss is detected, low otherwise gm_tie: std_logic o Outputted towards the controller control o High if a finished tie game is detected, low otherwise

45

46

gm_status: Check Current Game Status gm_win: std_logic o Outputted towards the controller control o High if AI win is detected, low otherwise

FIGURE 34: BLOCK DIAGRAM OF THE GM_STATUS COMPONENT

SIMULATIONS AND WAVEFORMS The following is a waveform (g27_gm_status1) showing the four situations: win, loss, tie, incomplete. The code is simple enough so one can assume the other cases would work as well.

FIGURE 35: SIMULATION WAVEFORM FOR THE GM_STATUS COMPONENT

PSEUDOCODE
if(curr_state == status or curr_state == ai_status){ if(curr_state == status){ mark= "01"; }else { mark= "11"; } // if there exists a if (ga_array_in(0,0) mark) OR (ga_array_in(1,0) == mark) OR (ga_array_in(2,0) == mark) OR completed row full of "X" marks, then the HP has won == mark AND ga_array_in(0,1) == mark AND ga_array_in(0,2) == mark AND ga_array_in(1,1) == mark AND ga_array_in(1,2) == mark AND ga_array_in(2,1) == mark AND ga_array_in(2,2) ==

hp_wait: Wait for HP Move


(ga_array_in(0,0) == mark AND mark) OR (ga_array_in(0,1) == mark AND mark) OR (ga_array_in(0,2) == mark AND mark) OR (ga_array_in(0,0) == mark AND mark) OR (ga_array_in(0,2) == mark AND mark){ if(curr_state == status){ gm_loss = '1'; gm_tie = '0'; gm_win = '0'; }else { gm_loss = '0'; gm_tie = '0'; gm_win = '1'; } ga_array_in(1,0) == mark AND ga_array_in(2,0) == ga_array_in(1,1) == mark AND ga_array_in(2,1) == ga_array_in(1,2) == mark AND ga_array_in(2,2) == ga_array_in(1,1) == mark AND ga_array_in(2,2) == ga_array_in(1,1) == mark AND ga_array_in(2,0) ==

47

} else if(ga_array_in(0,0)(0) == '1' AND ga_array_in(0,1)(0) == '1' AND ga_array_in(0,2)(0) == '1' )AND (ga_array_in(1,0)(0) == '1' AND ga_array_in(1,1)(0) == '1' AND ga_array_in(1,2)(0) == '1') AND (ga_array_in(2,0)(0) == '1' AND ga_array_in(2,1)(0) == '1' AND ga_array_in(2,2)(0) == '1') AND (ga_array_in(0,0)(0) == '1' AND ga_array_in(1,0)(0) == '1' AND ga_array_in(2,0)(0) == '1') AND (ga_array_in(0,1)(0) == '1' AND ga_array_in(1,1)(0) == '1' AND ga_array_in(2,1)(0) == '1') AND (ga_array_in(0,2)(0) == '1' AND ga_array_in(1,2)(0) == '1' AND ga_array_in(2,2)(0) == '1') AND (ga_array_in(0,0)(0) == '1' AND ga_array_in(1,1)(0) == '1' AND ga_array_in(2,2)(0) == '1') AND (ga_array_in(0,2)(0) == '1' AND ga_array_in(1,1)(0) == '1' AND ga_array_in(2,0)(0) == '1'){ gm_loss = '0'; gm_tie = '1'; gm_win = '0'; }else { gm_loss = '0'; gm_tie = '0'; gm_win = '0'; } }else { gm_loss = '0'; gm_tie = '0'; gm_win = '0'; }

HP_WAIT: WAIT FOR HP MOVE

48

hp_wait: Wait for HP Move DESCRIPTION The hp_wait component is responsible for the capture of the human players moves. As such, it is active only during the hp_move state. At every rising edge of the clock, the component checks if the user has pressed the set button, indicating that the human player wishes to add an X mark to the game table ga_array in the user-specified row row_id and the column col_id. If the location desired is invalid (row_id = 11 or col_id = 11) or if it is non-empty (i.e. ga_array[row_id][col_id] has non-zero value), then nothing happens as the output control signal hp_mademove is set to 0 to indicate that the human player has not made a move yet. If the location specified is empty, then the row and column indices are passed on to their corresponding outputs and hp_mademove is set to 1 to show that the human player has made a move. DESCRIPTION OF THE I/O PINS Input Pins: clk: std_logic o The clock, generated by the 24MHz Oscillator on the Altera board curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks set: std_logic o User input (push button) o Indicates that the human player wants to make a move (active low) hp_row_in: std_logic_vector(1 downto 0) o User input (push button)

hp_wait: Wait for HP Move o 2-bit representation of the currently selected row by the user hp_col_in: std_logic_vector(1 downto 0) o User input (push button) o 2-bit representation of the currently selected column by the user Output Pins: row_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The row index of the newly inserted X mark col_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The column index of the newly inserted X mark hp_mademove: std_logic o Outputted towards the game table component ga_array o Indicates that the human player has successfully made a move

49

FIGURE 36: BLOCK DIAGRAM OF THE HP_WAIT COMPONENT

SIMULATIONS AND WAVEFORMS To test the functionality of the hp_wait component, we made a simple test covering each case we are interested in. The results are shown in the waveform below. In this test, the user tries to set an X mark in the game table at row 2, column 1 in a table where the cell at that position is empty initially.

50

hp_wait: Wait for HP Move During the first two clock cycles, the user tries to set an X mark when the current state of the finite state machine is still not hp_move. As such, the output hp_mademove is not set to 1 and as such the state remains hp_move. In the third clock cycle, we check what happens if the user does not press on the set button, that is, the set signal is set to 1. As expected, the hp_mademove signal remains at 0. In the fourth clock cycle, we simulate the user pressing the button by setting the set input to 0. Finally, the hp_mademove signal is set to 1 with correct row_id and col_id, indicating that the user has made a move to place an X in the location specified by the row and column indices. In the fifth clock cycle, we update the game table by giving a value of 1 to the cell at row 2 and column 1. The hp_mademove then is set to 0 since the component detects that the location specified is non-empty. Finally, in the sixth clock cycle, the user inputs an invalid location: row 11 and column 01. The function detects this, and still does not indicate a human player move.

FIGURE 37: SIMULATION WAVEFORM OF THE HP_WAIT COMPONENT

PSEUDOCODE
if (curr_state == hp_move){ made_move = '0'; // // // // If the inputs for row and col id are valid (i.e. they can only have value 00, 01 or 10) then if the user has the set button pressed, add an "X" mark to the row and column specified in the ga_array

ai_find_win: Try to Win Or Block


if (hp_row_in != "11" and hp_col_in != "11"){ if (set == '0'){ if (ga_array_in(row, col) == "00"){ // If the enter button is pressed, check to see if // the cell can be written to // If it can, add x to ga_array row_id = row; col_id = col; made_move = '1'; } } } }else { made_move = '0'; } hp_mademove = made_move;

51

AI_FIND_WIN: TRY TO WIN OR BLOCK DESCRIPTION The ai_find_win component is responsible for two parts of the AIs playing strategy, which are independently called based on the current state of the finite state machine in the controller. During the ai_try_win state, the AI checks the game table ga_array_in to see if it can win in his current turn. The pattern is trivial: any horizontal, vertical or diagonal row that contains two O marks (value 11) and one empty space (value 00) is a potential winning row. When such a row is found, the component outputs the row index row_id and column index col_id of the empty cell, filling the row with three O marks and hence winning the game. It also sets the control signal ai_mademove to 1 to signal that the AI is making a move. If no nearly complete row is found, ai_mademove is set to 0 and the controller changes to its next strategy state on the next clock cycle. During the ai_try_block state, the AI checks ga_array_in to stop the human from making a winning move in his next turn. In this state, we look for the same pattern as above, except we look for rows containing X marks (value 01) instead of O marks. When a good row is found, the component outputs the row index row_id and column index col_id of the empty

52

ai_find_win: Try to Win Or Block cell, blcoking the row with the O mark and hence preventing a human player win. It also sets the control signal ai_mademove to 1 to signal that the AI is making a move. If no nearly complete row of X marks is found, ai_mademove is set to 0 and the controller changes to its next strategy state on the next clock cycle. DESCRIPTION OF THE I/O PINS Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks Output Pins: row_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The row index of the newly inserted X mark col_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The column index of the newly inserted X mark ai_mademove: std_logic o Outputted towards the game table component ga_array o Indicates that the AI has successfully made a move

ai_find_win: Try to Win Or Block

53

FIGURE 38: BLOCK DIAGRAM OF THE AI_FIND_WIN COMPONENT

SIMULATIONS AND WAVEFORMS We did a few simulations to see if the ai_find_win component works as intended. First, we made sure that all possible winning rows can be detected and that the empty cell that needs to be filled so that the row becomes complete or is blocked. In the waveform below, we set the state to be ai_try_win) and tested the case of horizontal rows, then vertical rows, then finally diagonal rows. Upon inspection of the outputs row_id and col_id with the location of the empty cell to be filled, we confirm that all possible row completions can be detected. Also, the output signal ai_mademove is set at 1 after each try, showing that the AI has made its move through the ai_find_win component.

FIGURE 39: WAVEFORM OF AI_FIND_WIN COVERING ALL POSSIBLE WINNING ROWS

In the next waveform, we do the same simulation as before, except we replace all O marks (00) with X marks and set the state to ai_try_block. This way, the AI will add the O

54

ai_find_win: Try to Win Or Block mark in the same places specified in the first waveform, this time to block an impending win by the human player.

FIGURE 40: WAVEFORM OF AI_FIND_WIN COVERING ALL POSSIBLE LOSING ROWS

Finally, we check that the component works correctly when no valid rows are detected. In the waveform below, we cover the cases where the number of X marks in a row is 0, 1 and 2. In the last case (2 X marks), the last element of the row is an O mark. In all cases, nothing will happen as the output signal ai_mademove remains at 0.

FIGURE 41: WAVEFORM OF AI_FIND_WIN COVERING INVALID ROWS

PSEUDOCODE
if (curr_state == ai_try_win or curr_state == ai_try_block){ row_id_temp= 0; col_id_temp= 0; made_move= '0'; // If current state is ai_try_win, we're looking for rows

ai_find_win: Try to Win Or Block


// containing "O" marks that can be completed to win // If current state is ai_try_block, we're looking for rows // containing "X" marks that can be blocked to prevent opponent from winning if(curr_state == ai_try_win){ mark= "11"; }else { mark= "01"; } // Step 1 - Try to find a winning move // i.e. find a row containing two circles an one empty space L2: for(i = 0; i < 3; i++){ // find a nearly complete horizontal row if(ga_array_in(i,0) == "00" AND ga_array_in(i,1) == mark == mark){ row_id_temp= i; col_id_temp= 0; made_move= '1'; } else if( ga_array_in(i,0) == mark AND ga_array_in(i,1) ga_array_in(i,2) == mark){ row_id_temp= i; col_id_temp= 1; made_move= '1'; } else if( ga_array_in(i,0) == mark AND ga_array_in(i,1) ga_array_in(i,2) == "00"){ row_id_temp= i; col_id_temp= 2; made_move= '1'; // find a nearly complete vertical row } else if( ga_array_in(0,i) == "00" AND ga_array_in(1,i) ga_array_in(2,i) == mark){ row_id_temp= 0; col_id_temp= i; made_move= '1'; } else if( ga_array_in(0,i) == mark AND ga_array_in(1,i) ga_array_in(2,i) == mark){ row_id_temp= 1; col_id_temp= i; made_move= '1'; } else if( ga_array_in(0,i) == mark AND ga_array_in(1,i) ga_array_in(2,i) == "00"){ row_id_temp= 2; col_id_temp= i; made_move= '1'; } }

55

AND ga_array_in(i,2)

== "00" AND

== mark AND

== mark AND

== "00" AND

== mark AND

if made_move == '0'{ // find a nearly complete diagonal row if(ga_array_in(0,0) == "00" AND ga_array_in(1,1) == mark AND ga_array_in(2,2) == mark){ row_id_temp= 0; col_id_temp= 0; made_move= '1';

56

ai_fork: Try to Make a Fork


} else if( ga_array_in(0,0) == mark AND ga_array_in(1,1) == "00" AND ga_array_in(2,2) == mark){ row_id_temp= 1; col_id_temp= 1; made_move= '1'; } else if( ga_array_in(0,0) == mark AND ga_array_in(1,1) == mark AND ga_array_in(2,2) == "00"){ row_id_temp= 2; col_id_temp= 2; made_move= '1'; } else if( ga_array_in(0,2) == "00" AND ga_array_in(1,1) == mark AND ga_array_in(2,0) == mark){ row_id_temp= 0; col_id_temp= 2; made_move= '1'; } else if( ga_array_in(0,2) == mark AND ga_array_in(1,1) == "00" AND ga_array_in(2,0) == mark){ row_id_temp= 1; col_id_temp= 1; made_move= '1'; } else if(ga_array_in(0,2) == mark AND ga_array_in(1,1) == mark AND ga_array_in(2,0) == "00"){ row_id_temp= 2; col_id_temp= 0; made_move= '1'; } } // If a nearly complete row is found, AI adds an "O' mark to the empty cell of the row if(made_move == '1'){ ai_mademove = '1'; }else { // Otherwise, AI does not make a move ai_mademove = '0'; } // Pass on the row and column values as output to confirm the move row_id = row_id_temp; col_id = col_id_temp; }else { ai_mademove = '0'; row_id = 0; col_id = 0; }

AI_FORK: TRY TO MAKE A FORK DESCRIPTION The ai_fork component is active when the current state of the finite state machine is ai_try_fork. This component allows the AI to determine whether it can win in its next turn. To

ai_fork: Try to Make a Fork do so, it must create a fork, i.e. a situation where there exists two empty cells in the game table such that adding an O mark on either space results in an AI win. In code, we use a trial-anderror method: assume that the AI adds an O mark in the first empty space it encounters. If it is able to create a fork, the component updates its outputs by setting the row_id and col_id signals to select the space that was initially chosen. If no fork is found, the AI restarts, adding an O mark in the next empty cell it finds in the game table and the process above is executed again until a potential fork is found or until all empty spaces have been checked. As with all other components dealing with the AIs playing strategy, the ai_mademove signal is used to notify the controller that the AI has made a move during the ai_fork state. DESCRIPTION OF THE I/O PINS The ai_fork component has exactly the same pin configuration as the ai_try_win component (as do all the components handling the AI strategy). Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks Output Pins: row_id: integer range 0 to 2 o Outputted towards the game table component ga_array

57

58

ai_fork: Try to Make a Fork o The row index of the newly inserted X mark col_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The column index of the newly inserted X mark ai_mademove: std_logic o Outputted towards the game table component ga_array o Indicates that the AI has successfully made a move

FIGURE 42: BLOCK DIAGRAM OF THE AI_FORK COMPONENT

SIMULATIONS AND WAVEFORMS To test the ai_fork component, we simulate different board combinations and see if the AI is able to find a fork. Since our code heavily borrows from the ai_find_win component, we do not need to test all possible fork combinations. Hence, we shall only test the behaviour of the component in the following conditions: A fork can be created due to two winning moves being found after adding the O mark No fork can be created due to only one winning move being found after adding the O mark No fork can be created and no winning moves are found

FIGURE 43: WAVEFORM FOR AI_FORK

In the above waveform, we apply the forking algorithm in different situations:

ai_fork: Try to Make a Fork In the first clock cycle, the AI successfully adds an O mark in the bottom-right corner, creating a fork from the bottom horizontal and right-most diagonal rows. It acknowledges the move by setting the ai_mademove signal high. We tried another fork possibility in the third clock cycle. In the second clock cycle, the whole game table is full; hence, no winning move can be made by adding an O mark since no new O mark can be added. Therefore, no move is made from the ai_fork component (ai_mademove = 0). In the fourth test, we look at the case where the AI can find one winning move after adding an O mark in an empty space (in this case, cells (1,1) and (1,2)). Since a fork is created when at least two winning moves are found, the AI recognises this and drops this possibility. But there are no forks that can be created in this circumstance, so ai_mademove is set at 0. Finally, the case of an empty table is investigated. There are obviously no forks that can be made since a minimum of three O marks need to be on the field for a fork to exist. Fortunately, the AI catches this and does not decide to make a move with this component. PSEUDOCODE
if(curr_state == ai_try_fork){ // Sweep through all possible next moves (i.e. any empty cell) // until a fork is found L1: for(i = 0; i < 3; i++){ L2: for(j = 0; j < 3; j++){ if(ga_array_in(i,j) == "00"){ made_move= 0; ga_array_temp= ga_array_in; ga_array_temp(i,j)= "11"; // Count the number of nearly complete rows that can be completed in the next turn L3: for(k = 0; k < 3; k++){ // find a nearly complete horizontal row if(ga_array_temp(k,0) == "00" AND ga_array_temp(k,1) == "11" AND ga_array_temp(k,2) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(k,0) == "11" AND ga_array_temp(k,1) == "00" AND ga_array_temp(k,2) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(k,0) == "11" AND ga_array_temp(k,1) == "11" AND ga_array_temp(k,2) == "00"){

59

60

ai_fork: Try to Make a Fork


made_move= made_move + 1; } // find a nearly complete vertical row if(ga_array_temp(0,k) == "00" AND ga_array_temp(1,k) == "11" AND ga_array_temp(2,k) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(0,k) == "11" AND ga_array_temp(1,k) == "00" AND ga_array_temp(2,k) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(0,k) == "11" AND ga_array_temp(1,k) == "11" AND ga_array_temp(2,k) == "00"){ made_move= made_move + 1; } } // find a nearly complete diagonal row if(ga_array_temp(0,0) == "00" AND ga_array_temp(1,1) == "11" AND ga_array_temp(2,2) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(0,0) == "11" AND ga_array_temp(1,1) == "00" AND ga_array_temp(2,2) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(0,0) == "11" AND ga_array_temp(1,1) == "11" AND ga_array_temp(2,2) == "00"){ made_move= made_move + 1; } if(ga_array_temp(0,2) == "00" AND ga_array_temp(1,1) == "11" AND ga_array_temp(2,0) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(0,2) == "11" AND ga_array_temp(1,1) == "00" AND ga_array_temp(2,0) == "11"){ made_move= made_move + 1; } else if(ga_array_temp(0,2) == "11" AND ga_array_temp(1,1) == "11" AND ga_array_temp(2,0) == "00"){ made_move= made_move + 1; } // If 2 or more winning rows can be made, AI can create a fork // Confirm the row & column coordinates of the inserted O mark creating the fork if(made_move > 1){ row_id = i; col_id = j; ai_mademove ='1'; exit L1; // If no fork is found after cycling through all possible next moves, do not make a move }else { row_id = 0; col_id = 0; ai_mademove = '0'; } }else {

ai_block_fork: Try to Block a Fork


row_id = 0; col_id = 0; ai_mademove = '0'; } } } }else { row_id = 0; col_id = 0; ai_mademove = '0'; }

61

AI_BLOCK_FORK: TRY TO BLOCK A FORK DESCRIPTION The block fork circuits job is to determine possible forks that the human player (HP) can use on their next move and to prevent these forks. Its placed 4th circuit in hierarchy in the FSM for the AI move decision making task. The circuit is activated if the curr_state signal is equal to ai_try_blockfork. It does not follow the clock of the circuit directly since it is designed to follow only any change of state. If the circuit detects a possible fork, the circuit outputs the coordinates of the cell where the AI should insert an O mark to prevent the possible fork and also setting the signal ai_mademove high to signal that the AI made a move. If the circuit does not detect any possible fork, then it outputs the signal ai_mademove as low indicating that the circuit failed and the FSM should jump to the next state. The algorithm used to detect forks is as follow: The circuit will scan the input tic-tac-toc table STD_TABLE row by row, column by column, and the two diagonals. The circuit searches for instances of rows where only one X mark are within (with two empty cells) and marks these rows as possible parts for a fork. When all rows are checked, the circuit scans the table cell by cell for instances of cells which happen to be the intersection points of two or more rows which have been marked as possible parts of a fork. If a cell happens to have such characteristic, then a mark X at such location would create a fork to the HP advantage.

62

ai_block_fork: Try to Block a Fork

To prevent the possible fork, the circuit will insert an O mark at the fork cell for normal cases. However there are special cases of forks which will not be prevented by simply filling the cell where the fork can happen. These cases and how they are prevented are presented in the images below.

DESCRIPTION OF THE I/O PINS The ai_fork component has exactly the same pin configuration as the ai_try_win component (as do all the components handling the AI strategy). Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks Output Pins: row_id: integer range 0 to 2 o Outputted towards the game table component ga_array

ai_block_fork: Try to Block a Fork o The row index of the newly inserted X or O mark col_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The column index of the newly inserted X or O mark ai_mademove: std_logic o Outputted towards the game table component ga_array o Indicates that the AI has successfully made a move

63

FIGURE 44: BLOCK DIAGRAM OF THE AI_BLOCK_FORK COMPONENT

SIMULATIONS AND WAVEFORMS In the first waveform (fork1), a few sample cases of forks are shown to be detected and the output coordinates row_id and col_id correspond to the cell where the O mark needs to be inserted to prevent the fork. Note that in certain of the test cases the AI could have directly picked a different coordinate to immediately win the game, but for the sake of this circuit, we will only consider the fork blocking process.

FIGURE 45: FIRST SIMULATION WAVEFORM FOR THE AI_BLOCK_FORK COMPONENT

The second waveform (fork2) shows when the circuit fails to detect any fork and thus the ai_mademove signal is low and the outputs coordinates will be ignored. The first clock cycle was used to show that the circuit is still performing its assigned task. In the following clock cycles, the cases tested are:

64

ai_block_fork: Try to Block a Fork 1. Only 1 O and 1 X on GA 2. Only 1 X on GA 3. A full GA 4. An empty GA 5. A few random cases. All cases demonstrated that the circuit failed to find a possible fork for the HP, which is correct for these test cases.

FIGURE 46: SECOND SIMULATION WAVEFORM FOR THE AI_BLOCK_FORK COMPONENT

The third waveform (fork3) tests the cases for the special forks mentioned in the figures previously presented. As expected, the AI was not duped by placing the O mark directly on top of the fork cell, but responded by an acceptable placement of the O mark which force the HP to respond to the threat of a two O marks row.

FIGURE 47: THIRD SIMULATION WAVEFORM FOR THE AI_BLOCK_FORK COMPONENT

PSEUDOCODE
if (curr_state == ai_try_blockfork ) { // Since we are blocking, we try to find 'X' marks, but if needed, this code can be used // by the AI to find potential forks with mark 'O'. It can be done like so: // if(curr_state == ai_try_fork) // mark == "11"; // else if(curr_state == ai_try_blocking) // mark == "10"; // }

ai_block_fork: Try to Block a Fork

65

mark = "10"; r0= FALSE; r1= FALSE; r2= FALSE; c0= FALSE; c1= FALSE; c2= FALSE; d0= FALSE; d1 = FALSE; GA_t = GA; count_t_l_r = 0; count_t_r_l = 0; // Checking for rows where there is only one target mark (while the 2 other cells are empty) // The algorithm uses addition of integers to determine the amount of empty and mark elements // in each row. for(int i=0; i<3; i++){ count_t_row = 0; count_t_col = 0;

for(int j=0; j<3; j++){ // checking the horizontal rows if(GA(i,j) == "00"){ count_t_row = count_t_row + }else if(GA(i,j) == mark){ count_t_row = count_t_row + } // checking the columns if(GA(j,i) == "00"){ count_t_col = count_t_col + }else if{(GA(j,i) == mark) count_t_col = count_t_col + }

3; 1;

3; 1;

// only check the diagonals once. if(i == 0){ // checking the left to right diagonal (left bottom corner to upper right corner) if(GA_t(j,j) == "00"){ count_t_l_r = count_t_l_r + 3; } else if(GA_t(j,j) == mark){ count_t_l_r = count_t_l_r + 1; } //checking the right to left diagonal if(GA_t(2-j,j) == "00"){ count_t_r_l = count_t_r_l + 3; }else if(GA_t(2-j,j) == mark){ count_t_r_l = count_t_r_l + 1;

66

ai_block_fork: Try to Block a Fork


} } } // If the sum of the integers represent a row with only one mark and two empty cells, // that row is a potential candidate for a fork. if(i == 0){ if(count_t_row == 7){ r0 = true; } if(count_t_col == 7){ c0 = true; } } if(i == 1){ if(count_t_row == 7){ r1 = true; } if(count_t_col == 7){ c1 = true; } } if(i == 2){ if(count_t_row == 7){ r2 = true; } if(count_t_col == 7){ c2 = true; } } end case; } // The row evaluation for the diagonals can be done only once. if(count_t_l_r == 7){ d0 = true; } if(count_t_r_l == 7){ d1 = true; } ai_mademove = '1'; // here we assume that no row contains 2 of the same mark since this case is covered // by a component of higher hierarchy. // tricky forks from HP // When one 'O' mark in a corner and two 'X' marks on the same diagonal as the 'O' mark. if(r1 AND c1 AND((r2 AND c2)OR(r0 AND c0)) AND mark == "10" AND GA_t(0,2) == "00"){ row_id = 0; col_id = 2;

ai_direct_write: Other Strategies


}else if(r1 AND c1 AND((r2 AND c0)OR(r0 AND c2)) AND mark == "10" AND GA_t(0,2) == "00"){ row_id = 0; col_id = 0; // When one 'O' mark in the middle and two 'X' marks on opposite corners }else if(r0 AND c0 AND r2 AND c2 AND mark == "10" AND GA_t(0,1) == "00"){ row_id = 0; col_id = 1; // normal forks below }else if(((r0 AND c0) OR ((r0 OR c0)AND(d0))) AND GA_t(0,0) == "00"){ row_id = 0; col_id = 0; }else if(r0 AND c1 AND GA_t(0,1) == "00"){ row_id = 0; col_id = 1; }else if(((r0 AND c2) OR ((r0 OR c2)AND(d1))) AND GA_t(0,2) == "00"){ row_id = 0; col_id = 2; }else if(r1 AND c0 AND GA_t(1,0) == "00"){ row_id = 1; col_id = 0; }else if(((r1 AND c1) OR ((r1 OR c1)AND(d0 OR d1)) OR (d0 AND d1)) AND GA_t(1,1) == "00"){ row_id = 1; col_id = 1; }else if(d0 AND d1 AND GA_t(1,1) == "00"){ row_id = 1; col_id = 1; }else if(r1 AND c2 AND GA_t(1,2) == "00"){ row_id = 1; col_id = 2; }else if(((r2 AND c0) OR ((r2 OR c0)AND(d1))) AND GA_t(2,0) == "00"){ row_id = 2; col_id = 0; }else if(r2 AND c1 AND GA_t(2,1) == "00"){ row_id = 2; col_id = 1; }else if(((r2 AND c2) OR ((r2 OR c2)AND(d0))) AND GA_t(2,2) == "00"){ row_id = 2; col_id = 2; // when all possibilities fail, move incomplete. }else{ ai_mademove = '0'; } }else{ ai_mademove = '0'; }

67

AI_DIRECT_WRITE: OTHER STRATEGIES

68

ai_direct_write: Other Strategies DESCRIPTION The ai_direct_write component is the final element of the AIs strategy on all but the lowest difficulty level. It is active only during the ai_try_direct state. The strategy behind this component follows this order: If the centre cell has not been taken already, take it. Otherwise, check if the opponent has played in a corner opposing an empty corner. If so, play on that empty corner. Otherwise, take any available (i.e. empty) corner If all corners are taken, take any available side cell (any non-centre and non-corner cell)

As with all other components dealing with the AIs playing strategy, the ai_mademove signal is used to notify the controller that the AI has made a move during the ai_try_direct state, stopping the AI strategy (although its impossible for this not to happen unless the game table is filled, in which case the status state will signal the tie before giving a chance for the AI strategy to start again. DESCRIPTION OF THE I/O PINS The ai_direct_write component has exactly the same pin configuration as the ai_try_win component (as do all the components handling the AI strategy). Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component

ai_direct_write: Other Strategies o Representation of the current state of the game table o Used to locate all the X and O marks Output Pins: row_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The row index of the newly inserted X mark col_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The column index of the newly inserted X mark ai_mademove: std_logic o Outputted towards the game table component ga_array o Indicates that the AI has successfully made a move

69

FIGURE 48: BLOCK DIAGRAM OF THE AI_DIRECT_WRITE COMPONENT

SIMULATIONS AND WAVEFORMS The ai_direct_write component is very easy to test since the logic represents a series of cascading if statements. First, we test if the first part of the strategy, that is, taking the centre, is working. In the first waveform, we tested this case in the first clock cycle. As expected, the circuit works as the AI signals its move by setting the ai_mademove output to 1 with the centre cell coordinates (row_id = 1, col_id = 1) as output. In the next clock cycles, the centre is non-empty, allowing for the rest of the strategy to run. Here, we place an X mark in one corner of the game table. Consequently, the AI places an

70

ai_direct_write: Other Strategies O mark in the corner cell opposite the corner where the human player has placed an X mark.

FIGURE 49: WAVEFORM OF AI_DIRECT_WRITE FOR THE FIRST TWO SUBSTRATEGIES

Next, we block opposing corners and observe that the AI proceeds with the next part of its strategy: playing in any available corner. We observe that this is the case in the first part of the second waveform. Finally, we test the final part of the strategy by forcing the AI to play on a side cell. Once again, we see that the component is functioning as it should.

FIGURE 50: WAVEFORM OF AI_DIRECT_WRITE FOR THE LAST TWO SUBSTRATEGIES

PSEUDOCODE
if(curr_state == ai_try_direct){ // Step 1 - Take the Centre if available if(ga_array_in(1,1) == "00"){ row_id_temp= 1; col_id_temp= 1; // Step 2 - If the opponent has taken a corner // take the opposite corner if it's available } else if(ga_array_in(0,0) == "01" AND ga_array_in(2,2) == "00"){ row_id_temp= 2; col_id_temp= 2; } else if(ga_array_in(0,2) == "01" AND ga_array_in(2,0) == "00"){ row_id_temp= 2; col_id_temp= 0; } else if(ga_array_in(2,0) == "01" AND ga_array_in(0,2) == "00"){ row_id_temp= 0; col_id_temp= 2; } else if(ga_array_in(2,2) == "01" AND ga_array_in(0,0) == "00"){

ai_random: Very Dumb AI


row_id_temp= 0; col_id_temp= 0; // Step 3 - Take the first } else if(ga_array_in(0,0) row_id_temp= 0; col_id_temp= 0; } else if(ga_array_in(0,2) row_id_temp= 0; col_id_temp= 2; } else if(ga_array_in(2,0) row_id_temp= 2; col_id_temp= 0; } else if(ga_array_in(2,2) row_id_temp= 2; col_id_temp= 2; // Step 4 - Take the first } else if(ga_array_in(0,1) row_id_temp= 0; col_id_temp= 1; } else if(ga_array_in(1,2) row_id_temp= 1; col_id_temp= 2; } else if(ga_array_in(1,0) row_id_temp= 1; col_id_temp= 0; } else if(ga_array_in(2,1) row_id_temp= 2; col_id_temp= 1; } else{ row_id_temp= 0; col_id_temp= 0; }

71

corner that is empty == "00"){

== "00"){

== "00"){

== "00"){

empty side == "00"){

== "00"){

== "00"){

== "00"){

// Add a circle by specifying the proper row & column ids and setting ai_mademove to 1 row_id = row_id_temp; col_id = col_id_temp; ai_mademove = '1'; } else{ // If not in the ai_try_direct, avoid making a move ai_mademove = '0'; row_id = 0; col_id = 0; }

AI_RANDOM: VERY DUMB AI DESCRIPTION

72

ai_random: Very Dumb AI The random circuits job is simply filling out the first available free cell with an O mark when no other strategies or counter strategies can be applied. The circuit will search for such a cell from left to right and bottom to top. The circuit is activated if the curr_state signal is equal to ai_random. It does not follow the clock of the circuit directly since it is designed to follow only any change of state. When all other strategies are unavailable, the circuit will simply fill out the first available cell in the order presented on the image below.

FIGURE 51: SCANNING ORDER OF THE AI_RANDOM COMPONENT

DESCRIPTION OF THE I/O PINS The ai_fork component has exactly the same pin configuration as the ai_try_win component (as do all the components handling the AI strategy). Input Pins: curr_state: state_type o A one-hot encoded value representing the current state of the finite state machine. o Synchronised to the clock via the controller. Hence, acts as a clock. o At most, the function will only execute once during a clock cycle: at the change in state ga_array_in: std_table o Outputted by the ga_array component o Representation of the current state of the game table o Used to locate all the X and O marks Output Pins:

ai_random: Very Dumb AI row_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The row index of the newly inserted O mark col_id: integer range 0 to 2 o Outputted towards the game table component ga_array o The column index of the newly inserted O mark ai_mademove: std_logic o Outputted towards the game table component ga_array o Indicates that the AI has successfully made a move

73

FIGURE 52: BLOCK DIAGRAM OF THE AI_RANDOM COMPONENT

SIMULATIONS AND WAVEFORMS The waveform presented below simply demonstrates what was explained previously. The outputted row_id and col_id coordinates indicate correctly the first cell available in each presented cases. In the last case, when the GA is filled, the circuit fails (ai_mademove = 0) and no O mark will be inserted. For the sake of showcasing this circuit, please ignore the fact that certain test cases contain full rows of X marks.

FIGURE 53: SIMULATION WAVEFORM FOR THE AI_RANDOM COMPONENT

PSEUDOCODE

74

ai_random: Very Dumb AI


if(curr_state = ai_random){ ai_mademove = '1'; if(ga_array_in(0,0)="00"){ row_id = 0; col_id = 0; } else if(ga_array_in(0,1)="00"){ row_id = 0; col_id = 1; } else if(ga_array_in(0,2)="00"){ row_id = 0; col_id = 2; } else if(ga_array_in(1,0)="00"){ row_id = 1; col_id = 0; } else if(ga_array_in(1,1)="00"){ row_id = 1; col_id = 1; } else if(ga_array_in(1,2)="00"){ row_id = 1; col_id = 2; } else if(ga_array_in(2,0)="00"){ row_id = 2; col_id = 0; } else if(ga_array_in(2,1)="00"){ row_id = 2; col_id = 1; } else if(ga_array_in(2,2)="00"){ row_id = 2; col_id = 2; }else{ ai_mademove = '0'; row_id = 0; col_id = 0; } }else{ ai_mademove = '0'; row_id =0; col_id =0; }

PROPOSED MODIFICATIONS TO DESIGN


Upon the verified completion of the overall design given the requirements of the lab, we had some extra time to implement some special extra features (difficulty levels, AI able to strt the game, a pointer on the hex display showing the current location selected by the user). Due to the relative robustness of the design, such modifications were very easy to implement. However, we can still think of additional modifications we could have added to our design as follows:

ai_random: Very Dumb AI Our code is relatively coded at a high-level. Therefore, there is some performance loss at the expense of greater clarity in our code. This loss isnt too great since we are barely using any of the resources available to the Altera board. Still, it is possible to optimize the performance of the design and should be considered if it needs to be implemented on a smaller chip. The bulkiest component in our design is without a doubt the bus multiplexer ga_array_mux. Its possible to completely eliminate this . Since all our AI strategy components have the same inputs and outputs and active at different times (different state), we could have considered merging all of these entities together, eliminating the need of a multiplexer like the ga_array_mux component. Timing might be an issue though. The AI, while it can never lose and tries to win quite effectively, is not perfect. To be perfect, the AI must make its move such that it has the greatest chance of winning. However, doing this would involve hardcoding all of the AIs moves, which may improve playing performance but would affect our general understanding of the design.

75

PROBLEMS ENCOUNTERED
Outside of inevitable syntax errors and other problems that were easily taken care of, we had encountered more challenging errors during the integration stage of our overall design. We initially had a single common signal acting as inputs to the ga_array instead of a multiplexor. Even though we thought that the signal worked, due to the crcuit working for the AIs first move, the AI sometimes failed to place an O in the correct place. In the end, we were forced to use a mux component ga_array_mux to control the signals that we want to connect to the ga_array. First of all, our initial designs for each component were such that the process blocks described in those components were all executed at the edge of the clock. The result of this design was that, at the rising edge of the clock, all inputs were the values calculated in the previous state. This was the notably the case for the input game array ga_arrray_in of each component. This problem was fixed once we made all components run only upon a change in state rather than the edge of the clock.

76

ai_random: Very Dumb AI However, this solution led to the creation of unwanted inferred latches, which we removed in the code once we found them. This fixed the problem for good.

CONCLUSION
In the end, it was an interesting experience designing logical circuits on hardware. We learned much about the quirks of hardware description languages and how they compare to traditional computer programming. Although we struggled through the project integration stage, we gained a lot of knowledge about the process. In future projects, we would like to see some more low-level design rather than the simple project that was Tic-Tac-Toe.

Appendix I: Global Architecture of the Game (ttt.vhd)

77

APPENDIX

APPENDIX I: GLOBAL ARCHITECTURE OF THE GAME (TTT.VHD)

FIGURE 54: BLOCK DIAGRAM OF THE GAME

78

Appendix II: Finite State Machine APPENDIX II: FINITE STATE MACHINE
(SEE CONTROLLER SECTION TO VIEW IN DETAIL)

FIGURE 55: FINITE STATE MACHINE DIAGRAM FOR THE COMPLETE GAME

reset

sg hp_move status ai_status loss tie win ai_try_win ai_try_block ai_random ai_try_fork ai_try_blockfork ai_try_direct

You might also like