Upload
piohm
View
392
Download
3
Tags:
Embed Size (px)
DESCRIPTION
Property of Payom Meshgin;McGill University, Montreal, Quebec, Canada
Citation preview
ECSE 323: Digital System
Design
Payom Meshgin (260431193)
Yi Qing Xiao (260429342)
Group #27
4/16/2012
Laboratory 5: System Integration
Prof. Katarzyna Radecka
i 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
iii 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
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
v 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
1 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 “X”s and “O”s 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 AI’s 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
2 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.
3 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 player’s 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
4 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
5 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.
6 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.
7 FSM schematics
FIGURE 2: FSM DIAGRAM, LEVEL = 1
8 FSM schematics
FIGURE 3: FSM DIAGRAM, LEVEL = 2
9 FSM schematics
FIGURE 4: FSM DIAGRAM, LEVEL = 3
10 FSM schematics
FIGURE 5: FSM DIAGRAM, LEVEL = 4
11 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
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'){
13 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;
OTHER FIGURES
Here’s 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.
FIGURE 6: RTL DIAGRAM OF THE GAME CONTROLLER
ai_loss
ai_mm
ai_tie
ai_win
clk
hp_mm
hp_start
reset
start
level[1..0]
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
reset
clock
start
hp_mm
ai_mm
ai_loss
ai_tie
ai_win
hp_start
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
level[1..0]
fstate
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 Actual Time
From To
Worst-case tsu
5.648 ns reset fstate.ai_random
Worst-case tco
7.344 ns fstate.status cur_state.status
Worst-case th
0.085 ns level[0] 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 Name on
board Pin
Location
start SW8 PIN_M1
hp_start SW7 PIN_M2
hp_mm SW6 PIN_U11
ai_mm SW5 PIN_U12
ai_loss SW4 PIN_W12
ai_tie SW3 PIN_V12
ai_win SW2 PIN_M22
clock KEY0 PIN_R22
15 Simulation waveforms
level[1] SW1 PIN_L21
level[0] SW0 PIN_L22 FIGURE 9: SIMULATION OF THE CONTROLLER
OUTPUT PINS:
Signal Name on
board Pin
Location
cur_state.sg LEDR9 PIN_R17
cur_state.hp_move LEDR8 PIN_R18
cur_state.status LEDR7 PIN_U18
cur_state.ai_try_win LEDR6 PIN_Y18
cur_state.ai_try_block LEDR5 PIN_V19
cur_state.ai_try_fork LEDR4 PIN_T18
cur_state.ai_try_blockfork LEDR3 PIN_Y19
cur_state.ai_try_direct LEDR2 PIN_U19
cur_state.ai_random LEDR1 PIN_R19
cur_state.ai_status LEDR0 PIN_R20
cur_state.loss LEDG7 PIN_Y21
cur_state.tie LEDG6 PIN_Y22
cur_state.win LEDG5 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’: status => ai_try_win => ai_try_fork => ai_try_direct => ai_random
“level” = ‘2’: status => ai_try_block => ai_try_block_fork => ai_try_direct
“level” = ‘3’: 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.
17 Simulation waveforms
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
19 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
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 ai_try_direct
hp_move ai_random
status ai_status
ai_try_win loss
ai_try_block tie
ai_try_fork win
ai_try_blockfork 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
21 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
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 Assigned Pin
clk PIN_D12
reset PIN_T21
user_begin PIN_M22
user_col[1] PIN_M2
user_col[0] PIN_U11
user_lvl[1] PIN_L21
user_lvl[0] PIN_L22
user_row[1] PIN_L2
user_row[0] PIN_M1
user_set PIN_R21
user_show_array PIN_R22
user_start PIN_T22
user_toggle_marks PIN_U12
FIGURE 17: INPUT PIN ASSIGNMENTS FOR G27_TTT.VHD
Output pins:
Signal Assigned Pin
hex0_out[0] PIN_J2
hex0_out[1] PIN_J1
hex0_out[2] PIN_H2
hex0_out[3] PIN_H1
Signal Assigned Pin
hex0_out[4] PIN_F2
hex0_out[5] PIN_F1
hex0_out[6] PIN_E2
hex1_out[0] PIN_E1
23 ttt: Overall Design
Signal Assigned Pin
hex1_out[1] PIN_H6
hex1_out[2] PIN_H5
hex1_out[3] PIN_H4
hex1_out[4] PIN_G3
hex1_out[5] PIN_D2
hex1_out[6] PIN_D1
hex2_out[0] PIN_G5
hex2_out[1] PIN_G6
hex2_out[2] PIN_C2
hex2_out[3] PIN_C1
hex2_out[4] PIN_E3
hex2_out[5] PIN_E4
hex2_out[6] PIN_D3
hex3_out[0] PIN_F4
hex3_out[1] PIN_D5
hex3_out[2] PIN_D6
Signal Assigned Pin
hex3_out[3] PIN_J4
hex3_out[4] PIN_L8
hex3_out[5] PIN_F3
hex3_out[6] PIN_D4
state_out[9] PIN_R17
state_out[8] PIN_R18
state_out[7] PIN_U18
state_out[6] PIN_Y18
state_out[5] PIN_V19
state_out[4] PIN_T18
state_out[3] PIN_Y19
state_out[2] PIN_U19
state_out[1] PIN_R19
state_out[0] PIN_R20
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 LC Combinationals LC
Registers
|g27_ai_block_fork:ai_block_fork| 38 0
|g27_ai_direct_write:ai_direct_write| 8 0
|g27_ai_find_win:ai_find_win| 53 0
|g27_ai_fork:ai_fork| 98 0
|g27_ai_random:ai_random| 7 0
|g27_control:control| 25 13
|g27_ga_array:ga_array| 29 18
|g27_ga_array_mux:ga_array_mux| 80 0
|g27_gm_led:gm_led| 83 51
|g27_gm_status:gm_status| 50 0
|g27_hp_wait:hp_wait| 19 5
24 ttt: Overall Design
|g27_sys_led_state:sys_led_state| 8 10
Total (g27_ttt) 498 97 FIGURE 20: RESOURCE UTILISATION BY ENTITY
Type Slack Time From To
Worst-case tsu
8.021 ns user_show_array g27_gm_led:gm_led|hex2_o
ut[2]
Worst-case tco
11.521 ns g27_gm_led:gm_led|hex3_o
ut[6] hex3_out[6]
Worst-case th
0.494 ns user_lvl[1] g27_gm_led:gm_led|hex0_o
ut[5]
Clock Setup: 'clk'
29.169 ns
80.02 MHz ( period =
12.497 ns )
g27_ga_array:ga_array|ga_array[1][2][1]
g27_ga_array:ga_array|ga_array[0][1][0]
Clock Hold: 'clk'
0.445 ns
N/A g27_control:control|fstate.ti
e g27_control:control|fstate.ti
e 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 => "1000000000";
hp_move => "0100000000";
status => "0010000000";
ai_try_win => "0001000000";
ai_try_block => "0000100000";
ai_try_fork => "0000010000";
ai_try_blockfork => "0000001000";
ai_try_direct => "0000000100";
loss => "0000000010";
win => "0000000001";
tie => "0000000011";
ai_random => "0001111100";
25 ttt: Overall Design
ai_status => "1110000000";
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
27 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.
SYS_LED_STATE: LED CURRENT STATE DISPLAY
DESCRIPTION
This circuit’s 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 board’s 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";
29 sys_led_state: LED Current State Display
case tie: state_out = "0000000011"; case ai_random: state_out = "0001111100"; case ai_status: state_out = "1110000000"; } }
GM_LED: SEVEN SEGMENT LED DISPLAY
DESCRIPTION
This circuit’s 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)
31 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
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”).
33 sys_led_state: LED Current State Display
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= "1111111"; hex1= "1111111"; hex2= "1111111"; hex3= "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";
35 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 is high // Also, set mark to "01" so that only "X" marks are shown when this is the case if(mark_x_en == '1'){ mark= "01"; hex0= "1001000"; }else { // In HEX0, show "O" if the user_toggle_marks switch is low // Also, set mark to "11" so that only "O" marks are shown when this is the case mark= "11"; hex0= "0000001"; } // 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
37 ga_array: Game Table
The GA array circuit’s 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 GA’s 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
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.
39 ga_array_mux: Multiplexer for the Game Table
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 circuit’s 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
41 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
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
43 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.
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
45 gm_status: Check Current Game Status
This circuit’s 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
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 completed row full of "X" marks, then the HP has won if (ga_array_in(0,0) == mark AND ga_array_in(0,1) == mark AND ga_array_in(0,2) == mark) OR (ga_array_in(1,0) == mark AND ga_array_in(1,1) == mark AND ga_array_in(1,2) == mark) OR (ga_array_in(2,0) == mark AND ga_array_in(2,1) == mark AND ga_array_in(2,2) == mark) OR
47 hp_wait: Wait for HP Move
(ga_array_in(0,0) == mark AND ga_array_in(1,0) == mark AND ga_array_in(2,0) == mark) OR (ga_array_in(0,1) == mark AND ga_array_in(1,1) == mark AND ga_array_in(2,1) == mark) OR (ga_array_in(0,2) == mark AND ga_array_in(1,2) == mark AND ga_array_in(2,2) == mark) OR (ga_array_in(0,0) == mark AND ga_array_in(1,1) == mark AND ga_array_in(2,2) == mark) OR (ga_array_in(0,2) == mark AND ga_array_in(1,1) == mark AND ga_array_in(2,0) == mark){ if(curr_state == status){ gm_loss = '1'; gm_tie = '0'; gm_win = '0'; }else { gm_loss = '0'; gm_tie = '0'; gm_win = '1'; } } 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 player’s 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)
49 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
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
51 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;
AI_FIND_WIN: TRY TO WIN OR BLOCK
DESCRIPTION
The “ai_find_win” component is responsible for two parts of the AI’s 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
53 ai_find_win: Try to Win Or Block
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
55 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 AND ga_array_in(i,2) == 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) == "00" AND 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) == mark AND 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) == mark AND 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) == "00" AND 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) == mark AND ga_array_in(2,i) == "00"){ row_id_temp= 2; col_id_temp= i; made_move= '1'; } } 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
57 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-and-
error 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 AI’s 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”
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:
59 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"){
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 {
61 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'; }
AI_BLOCK_FORK: TRY TO BLOCK A FORK
DESCRIPTION
The block fork circuit’s job is to determine possible forks that the human player (HP) can use on
their next move and to prevent these forks. It’s 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”
63 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
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"; // }
65 ai_block_fork: Try to Block a Fork
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 + 3; }else if(GA(i,j) == mark){ count_t_row = count_t_row + 1; } // checking the columns if(GA(j,i) == "00"){ count_t_col = count_t_col + 3; }else if{(GA(j,i) == mark) count_t_col = count_t_col + 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;
67 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'; }
AI_DIRECT_WRITE: OTHER STRATEGIES
68 ai_direct_write: Other Strategies
DESCRIPTION
The “ai_direct_write” component is the final element of the AI’s 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 AI’s 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 it’s 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
69 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
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"){
71 ai_random: Very Dumb AI
row_id_temp= 0; col_id_temp= 0; // Step 3 - Take the first corner that is empty } else if(ga_array_in(0,0) == "00"){ row_id_temp= 0; col_id_temp= 0; } else if(ga_array_in(0,2) == "00"){ row_id_temp= 0; col_id_temp= 2; } else if(ga_array_in(2,0) == "00"){ row_id_temp= 2; col_id_temp= 0; } else if(ga_array_in(2,2) == "00"){ row_id_temp= 2; col_id_temp= 2; // Step 4 - Take the first empty side } else if(ga_array_in(0,1) == "00"){ row_id_temp= 0; col_id_temp= 1; } else if(ga_array_in(1,2) == "00"){ row_id_temp= 1; col_id_temp= 2; } else if(ga_array_in(1,0) == "00"){ row_id_temp= 1; col_id_temp= 0; } else if(ga_array_in(2,1) == "00"){ row_id_temp= 2; col_id_temp= 1; } else{ row_id_temp= 0; col_id_temp= 0; } // 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 circuit’s 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:
73 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
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:
75 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 isn’t 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”.
It’s 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 AI’s moves, which may improve playing performance but
would affect our general understanding of the design.
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
AI’s 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.
77 Appendix I: Global Architecture of the Game (ttt.vhd)
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
( S E E C O N T R O L L E R S E C T I O N T O V I E W I N D E T A I L )
FIGURE 55: FINITE STATE MACHINE DIAGRAM FOR THE COMPLETE GAME
sg
hp
_m
ove
sta
tus
los
stie
win
ai_
try_w
ina
i_try_
blo
ck
ai_
ran
do
ma
i_try_
fork
ai_
try_b
lockfo
rka
i_try_
dire
ct
reset
ai_
sta
tus