Introduction: FPGA Tic-Tac-Toe

Hello. I'm a first year computer engineering student at Cal Poly SLO whose currently taking Prof. Danowitz's CPE 133 (Digital Design) class, and this is my final project.

This Instructable will show you how to implement a Tic-Tac-Toe game in Verilog, on the Basys 3 FPGA board. The board's switches will be used as input, while output will be done over VGA (640x480).

Supplies

For the project, you'll need the following:

  1. Basys3 FPGA board
  2. VGA cable, monitor
  3. Installation of Xilinx Vivado

Step 1: Video Driver

The video driver is a slightly modified version of the one downloadable here (credit to embeddedthoughts.com).

From what I've ascertained, it outputs 480 horizontal lines in accordance with standard VGA timing. While each line is being drawn, the analog RGB levels can be changed to represent different pixels. Through this, an image is displayed on-screen.


`timescale 1ns / 1ps
// Video driver from embeddedthoughts.com
// https://embeddedthoughts.com/2016/07/29/driving-a-vga-monitor-using-an-fpga/
module vga_sync(
input wire clk, reset,
output wire hsync, vsync, video_on, p_tick,
output wire [9:0] x, y
);

// constant declarations for VGA sync parameters
localparam H_DISPLAY       = 640; // horizontal display area
localparam H_L_BORDER      =  48; // horizontal left border
localparam H_R_BORDER      =  16; // horizontal right border
localparam H_RETRACE       =  96; // horizontal retrace
localparam H_MAX           = H_DISPLAY + H_L_BORDER + H_R_BORDER + H_RETRACE - 1;
localparam START_H_RETRACE = H_DISPLAY + H_R_BORDER;
localparam END_H_RETRACE   = H_DISPLAY + H_R_BORDER + H_RETRACE - 1;

localparam V_DISPLAY       = 480; // vertical display area
localparam V_T_BORDER      =  10; // vertical top border
localparam V_B_BORDER      =  33; // vertical bottom border
localparam V_RETRACE       =   2; // vertical retrace
localparam V_MAX           = V_DISPLAY + V_T_BORDER + V_B_BORDER + V_RETRACE - 1;
    localparam START_V_RETRACE = V_DISPLAY + V_B_BORDER;
localparam END_V_RETRACE   = V_DISPLAY + V_B_BORDER + V_RETRACE - 1;

// mod-4 counter to generate 25 MHz pixel tick
reg [1:0] pixel_reg;
wire [1:0] pixel_next;
wire pixel_tick;

always @(posedge clk, posedge reset)
if(reset)
  pixel_reg <= 0;
else
  pixel_reg <= pixel_next;

assign pixel_next = pixel_reg + 1; // increment pixel_reg 
assign pixel_tick = (pixel_reg == 0); // assert tick 1/4 of the time

// registers to keep track of current pixel location
reg [9:0] h_count_reg, h_count_next, v_count_reg, v_count_next;

// register to keep track of vsync and hsync signal states
reg vsync_reg, hsync_reg;
wire vsync_next, hsync_next;
 
// infer registers
always @(posedge clk, posedge reset)
if(reset)
    begin
                    v_count_reg <= 0;
                    h_count_reg <= 0;
                    vsync_reg   <= 0;
                    hsync_reg   <= 0;
    end
else
    begin
                    v_count_reg <= v_count_next;
                    h_count_reg <= h_count_next;
                    vsync_reg   <= vsync_next;
                    hsync_reg   <= hsync_next;
    end

// next-state logic of horizontal vertical sync counters
always @*
begin
h_count_next = pixel_tick ? 
               h_count_reg == H_MAX ? 0 : h_count_reg + 1
       : h_count_reg;

v_count_next = pixel_tick && h_count_reg == H_MAX ? 
               (v_count_reg == V_MAX ? 0 : v_count_reg + 1) 
       : v_count_reg;
end

        // hsync and vsync are active low signals
        // hsync signal asserted during horizontal retrace
        assign hsync_next = h_count_reg >= START_H_RETRACE && h_count_reg <= END_H_RETRACE;
   
        // vsync signal asserted during vertical retrace
        assign vsync_next = v_count_reg >= START_V_RETRACE && v_count_reg <= END_V_RETRACE;


        // video only on when pixels are in both horizontal and vertical display region
        assign video_on = (h_count_reg < H_DISPLAY) && (v_count_reg < V_DISPLAY);
        // output signals
        assign hsync  = hsync_reg;
        assign vsync  = vsync_reg;
        assign x      = h_count_reg;
        assign y      = v_count_reg;
        assign p_tick = pixel_tick;
endmodule

Step 2: Cell Module

Each cell is implemented as a finite-state machine with three different states- N, X, and O. N corresponds to an output of 00, X an output of 01, and O an output of 11.

While in the N state, the cell is unset and won't display anything on the grid. However, if the corresponding switch is set and, depending on the turn, it will transition to X or O.

While in the X or O state, the cell displays its shape on the grid and outputs its status accordingly. All cell status outputs are two bits wide. The cell cannot transition from this state, unless the game is reset.

Nine individual cells are defined in the gameLogic module, corresponding to those on the grid.


`timescale 1ns / 1ps
typedef enum {N, X, O} states;

module Cell(
        input clk,
        input Sel,
        input Turn,
        input Reset,
        
        output reg [1:0] State
    );
    
    states PS, NS;
    
    always_ff @ (posedge clk) begin
        if (Reset)
            PS = N;
        else
            PS = NS;
    end
    
    always_comb begin
        case(PS)
            N   :   begin
                State = 'b00;
                
                if (Sel && ~Turn)
                    NS = X;
                else if (Sel && Turn)
                    NS = O;
                else
                    NS = PS;
            end
            
            X  :    begin
                State = 'b01;
                NS = PS;
            end
            
            O  :    begin
                State = 'b11;
                NS = PS;
            end
            
            default : begin
                NS = N;
            end
        endcase
    end
endmodule

Step 3: Video Elements

This module interfaces with the video driver, and is responsible for defining the shapes that get drawn. It operates in-sync with the video driver's 25 MHz clock, deciding which color pixel to draw (red, white, black) based on its vertical and horizontal coordinates.

Each shape has a conditional statement, defining the bounds in which it is drawn. Additionally, the representation of each cell is dependent on its current state (N, X, or O).

Several localparams are factored in to the drawing of each shape

  • hRes - Horizontal screen resolution, 650
  • vRes - Vertical screen resolution, 480
  • hBorder - Horizontal border from edge of screen, 100
  • vBorder - Vertical border from edge of screen, 20
  • hLinePos1 - Position of first horizonal grid line, vBorder + 147
  • hLinePos2 - Position of second horizontal grid line, (vRes - 20) - 147
  • vLinePos1 - Position of first horizonal grid line, hBorder + 147
  • vLinePos2 - Position of second horizontal grid line, (hRes - 100) - 147
  • sqBorder - Border between squares and grid, on all sides - 40
  • plsBorder - Horizontal border between vertical lign of plus sign and grid, 30

p_tick, hPos, and vPos are all taken from the vga_sync module. The conditional draw checks are all run within an always block, triggered by p_tick..

A mux at the end selects one of three colors to use (black, white, red).


`timescale 1ns / 1ps

module videoElements(
input clk,
input [17:0] Cells,
input[8:0] Color,
input Turn,

output hsync, vsync,
output [11:0] rgb
);

// Constants
localparam hRes = 640;
localparam vRes = 480;

localparam hBorder = 100;
localparam vBorder = 20; 

localparam hLinePos1 = (vBorder) + 147;
localparam hLinePos2 = (vRes - 20) - 147;

localparam vLinePos1 = (hBorder) + 147;
localparam vLinePos2 = (hRes - 100) - 147;

localparam sqBorder = 40;
localparam [4:0] plsBorder = 30;

localparam lineWeight = 2;

// Internal registers
reg [1:0] pDisp;

// Internal wires
wire [9:0] hPos, vPos;
wire p_tick;

//wire plsBorder = 30;

        // instantiate vga_sync
        vga_sync vga_sync_unit (
            .clk(clk), .reset(reset), .hsync(hsync), 
            .vsync(vsync), .video_on(video_on), .p_tick(p_tick), 
            .x(hPos), .y(vPos));
   
        // Draw elements on screen
        always @(posedge p_tick) begin     
                // Horizontal grid
                if (hPos > (hBorder) && hPos < (hRes - hBorder) &&
                   ((vPos > hLinePos1 - lineWeight && vPos < hLinePos1 + lineWeight) || 
                   (vPos > hLinePos2 - lineWeight && vPos < hLinePos2 + lineWeight)))
                   
                    pDisp = 1;
                    
                // Vertical grid
                else if (vPos > (vBorder) && vPos < (vRes - vBorder) &&
                    ((hPos > vLinePos1 - lineWeight && hPos < vLinePos1 + lineWeight) || 
                    (hPos > vLinePos2 - lineWeight && hPos < vLinePos2 + lineWeight)))
                    
                    pDisp = 1;
                    
                //  Cell 1
                else if (
                    ((hPos > hBorder + sqBorder + (Cells[1] ? 0 : plsBorder) && hPos < vLinePos1 - sqBorder - (Cells[1] ? 0 : plsBorder) &&
                    vPos > vBorder + sqBorder && vPos < hLinePos1 - sqBorder) ||
                    (hPos > hBorder + sqBorder && hPos < vLinePos1 - sqBorder &&
                    vPos > vBorder + sqBorder + (Cells[1] ? 0 : plsBorder) && vPos < hLinePos1 - sqBorder - (Cells[1] ? 0 : plsBorder))) && 
                    Cells[0])
                    
                    pDisp = {Color[0],1'b1};
                    
                //  Cell 2
                else if (
                    ((hPos > vLinePos1 + sqBorder + (Cells[3] ? 0 : plsBorder) && hPos < vLinePos2 - sqBorder - (Cells[3] ? 0 : plsBorder) &&
                    vPos > vBorder + sqBorder && vPos < hLinePos1 - sqBorder) ||
                    (hPos > vLinePos1 + sqBorder && hPos < vLinePos2 - sqBorder &&
                    vPos > vBorder + sqBorder + (Cells[3] ? 0 : plsBorder) && vPos < hLinePos1 - sqBorder - (Cells[3] ? 0 : plsBorder))) && 
                    Cells[2])
                    
                    pDisp = {Color[1],1'b1};
                    
                //  Cell 3
                else if (
                    ((hPos > vLinePos2 + sqBorder + (Cells[5] ? 0 : plsBorder) && hPos < (hRes - hBorder) - sqBorder - (Cells[5] ? 0 : plsBorder) &&
                    vPos > vBorder + sqBorder && vPos < hLinePos1 - sqBorder) ||
                    (hPos > vLinePos2 + sqBorder && hPos < (hRes- hBorder) - sqBorder &&
                    vPos > vBorder + sqBorder + (Cells[5] ? 0 : plsBorder) && vPos < hLinePos1 - sqBorder - (Cells[5] ? 0 : plsBorder))) && 
                    Cells[4])
                    
                    pDisp = {Color[2],1'b1};                  
                    
                //  Cell 4
                else if (
                    ((hPos > hBorder + sqBorder + (Cells[7] ? 0 : plsBorder) && hPos < vLinePos1 - sqBorder - (Cells[7] ? 0 : plsBorder) &&
                    vPos > hLinePos1 + sqBorder && vPos < hLinePos2 - sqBorder) ||
                    (hPos > hBorder + sqBorder && hPos < vLinePos1 - sqBorder &&
                    vPos > hLinePos1 + sqBorder + (Cells[7] ? 0 : plsBorder) && vPos < hLinePos2 - sqBorder - (Cells[7] ? 0 : plsBorder))) && 
                    Cells[6])
                    
                    pDisp = {Color[3],1'b1};
                    
                //  Cell 5
                else if (
                    ((hPos > vLinePos1 + sqBorder + (Cells[9] ? 0 : plsBorder) && hPos < vLinePos2 - sqBorder - (Cells[9] ? 0 : plsBorder) &&
                    vPos > hLinePos1 + sqBorder && vPos < hLinePos2 - sqBorder) ||
                    (hPos > vLinePos1 + sqBorder && hPos < vLinePos2 - sqBorder &&
                    vPos > hLinePos1 + sqBorder + (Cells[9] ? 0 : plsBorder) && vPos < hLinePos2 - sqBorder - (Cells[9] ? 0 : plsBorder))) && 
                    Cells[8])
                    
                    pDisp = {Color[4],1'b1};
                    
                //  Cell 6
                else if (
                    ((hPos > vLinePos2 + sqBorder + (Cells[11] ? 0 : plsBorder) && hPos < (hRes - hBorder) - sqBorder - (Cells[11] ? 0 : plsBorder) &&
                    vPos > hLinePos1 + sqBorder && vPos < hLinePos2 - sqBorder) ||
                    (hPos > vLinePos2 + sqBorder && hPos < (hRes- hBorder) - sqBorder &&
                    vPos > hLinePos1 + sqBorder + (Cells[11] ? 0 : plsBorder) && vPos < hLinePos2 - sqBorder - (Cells[11] ? 0 : plsBorder))) && 
                    Cells[10])
                    
                    pDisp = {Color[5],1'b1};                 
                    
                 //  Cell 7
                else if (
                    ((hPos > hBorder + sqBorder + (Cells[13] ? 0 : plsBorder) && hPos < vLinePos1 - sqBorder - (Cells[13] ? 0 : plsBorder) &&
                    vPos > hLinePos2 + sqBorder && vPos < (vRes - vBorder) - sqBorder) ||
                    (hPos > hBorder + sqBorder && hPos < vLinePos1 - sqBorder &&
                    vPos > hLinePos2 + sqBorder + (Cells[13] ? 0 : plsBorder) && vPos < (vRes - vBorder) - sqBorder - (Cells[13] ? 0 : plsBorder))) && 
                    Cells[12])
                    
                    pDisp = {Color[6],1'b1};
                    
                //  Cell 8
                else if (
                    ((hPos > vLinePos1 + sqBorder + (Cells[15] ? 0 : plsBorder) && hPos < vLinePos2 - sqBorder - (Cells[15] ? 0 : plsBorder) &&
                    vPos > hLinePos2 + sqBorder && vPos < (vRes - vBorder) - sqBorder) ||
                    (hPos > vLinePos1 + sqBorder && hPos < vLinePos2 - sqBorder &&
                    vPos > hLinePos2 + sqBorder + (Cells[15] ? 0 : plsBorder) && vPos < (vRes - vBorder) - sqBorder - (Cells[15] ? 0 : plsBorder))) && 
                    Cells[14])
                    
                    pDisp = {Color[7],1'b1};
                    
                //  Cell 9
                else if (
                    ((hPos > vLinePos2 + sqBorder + (Cells[17] ? 0 : plsBorder) && hPos < (hRes - hBorder) - sqBorder - (Cells[17] ? 0 : plsBorder) &&
                    vPos > hLinePos2 + sqBorder && vPos < (vRes - vBorder) - sqBorder) ||
                    (hPos > vLinePos2 + sqBorder && hPos < (hRes- hBorder) - sqBorder &&
                    vPos > hLinePos2 + sqBorder + (Cells[17] ? 0 : plsBorder) && vPos < (vRes - vBorder) - sqBorder - (Cells[17] ? 0 : plsBorder))) && 
                    Cells[16])
                    
                    pDisp = {Color[8],1'b1};                 
                    
                // Background
                else
                    pDisp = 0;
        end
        
        // output
        assign rgb = (pDisp[0]) ? (pDisp[1] ? 'hF00 : 'hFFF) : 'h000;
endmodule

Step 4: Game State

This module uses another finite-state machine, which has two states (PLAY, WIN). While in the PLAY state, it checks for matches between rows, columns, and diagonals. The two-bit status of all cells is concatenated and inputted as an 18-bit value. Only three matching, non-zero cells in a row are valid.

Once in the WIN state, the game will stay in the WIN state until it is reset. The winState output will also be set to 1, preventing any further input from being taken.

The colors are outputted as a 9-bit binary number, with each digit corresponding to a single cell to be colored. This is sent into the videoElements module which uses them to color the individual cells red.


`timescale 1ns / 1ps
typedef enum {PLAY, WIN} gameStates;

module gameState(
        input Reset,
        input clk,
        input [17:0] Cells,
        
        output reg winState,
        output reg [8:0] Color
    );
    
    gameStates PS, NS;
    
 always_ff @ (posedge clk) begin
        if (Reset)
            PS = PLAY;
        else
            PS = NS;
    end
    
    always_comb begin
        case(PS)
            PLAY   :   begin
                winState = 0;
                
                // Horizontal
                if (Cells[1:0] == Cells [3:2] && Cells [3:2] == Cells[5:4] && Cells[1:0] != 0) begin 
                    Color = 'b000000111;
                    NS = WIN;
                end
                
                else if (Cells[7:6] == Cells [9:8] && Cells[9:8] == Cells[11:10] && Cells[7:6] != 0) begin
                    Color = 'b000111000;
                    NS = WIN;
                end
                
                else if (Cells[13:12] == Cells [15:14] && Cells[15:14] == Cells[17:16] && Cells[13:12] != 0) begin
                    Color = 'b111000000;
                    NS = WIN;
                end
                
                
                // Vertical
                else if (Cells[1:0] == Cells [7:6] && Cells [7:6] == Cells[13:12] && Cells[1:0] != 0) begin 
                    Color = 'b001001001;
                    NS = WIN;
                end
                
                else if (Cells[3:2] == Cells [9:8] && Cells[9:8] == Cells[15:14] && Cells[3:2] != 0) begin
                    Color = 'b010010010;
                    NS = WIN;
                end
                
                else if (Cells[5:4] == Cells [11:10] && Cells[11:10] == Cells[17:16] && Cells[5:4] != 0) begin
                    Color = 'b100100100;
                    NS = WIN;
                end
                
                 // Diagonal
                else if (Cells[1:0] == Cells [9:8] && Cells [9:8] == Cells[17:16] && Cells[1:0] != 0) begin 
                    Color = 'b100010001;
                    NS = WIN;
                end
                
                else if (Cells[5:4] == Cells [9:8] && Cells[9:8] == Cells[13:12] && Cells[5:4] != 0) begin
                    Color = 'b001010100;
                    NS = WIN;
                end
                    Color = 'b001010100;
                    NS = WIN;
                end
                
                else begin
                    Color = 0;
                    NS = PS;       
                end
            end
            
            WIN  :    begin
                winState = 1;
                NS = WIN;
            end
            
            default : begin
                Color = 0;
                winState = 0;
                NS = PLAY;
            end
            
        endcase
    end
endmodule

Step 5: Game Logic

The game logic module ties everything together- input, graphics, cells, and game state. It also contains a routine that synchronously alternates between turns (X and O).

All nine cells are individually defined. Input is taken within an always_comb block, with priority given to the highest-value switch. If winState = 1, input is blocked. The individual cell selected by the input block is defined in sqrSel.

hsync, vsync, and rgb are all outputted from the videoElements module. winState is outputted from the gameStates module. Switch input and Reset is all taken directly from the board, as defined in the constraints file.


`timescale 1ns / 1ps

module gameLogic(
    // Video
    input wire clk,
output wire hsync, vsync,
output wire [11:0] rgb,
output wire winState,

// Control
input [8:0] In,
input Reset
    );
    
    reg [8:0] sqrSel;
    reg [17:0] Cells;
    reg [8:0] prevIn;
    reg [8:0] myIn;
    reg Turn;
    
    //wire winState;
    wire [8:0] Color;
    
    // Input decoding
    always_comb begin
        if(!winState) begin
            if (In[6] && !Cells[16])             sqrSel = 'b100000000;
            else if (In[7] && !Cells[14])      sqrSel = 'b010000000;
            else if (In[8] && !Cells[12])      sqrSel = 'b001000000;
            
            else if (In[3] && !Cells[10])      sqrSel = 'b000100000;
            else if (In[4] && !Cells[8])        sqrSel = 'b000010000;
            else if (In[5] && !Cells[6])        sqrSel = 'b000001000;
            
            else if (In[0] && !Cells[4])        sqrSel = 'b000000100;
            else if (In[1] && !Cells[2])        sqrSel = 'b000000010;
            else if (In[2] && !Cells[0])        sqrSel = 'b000000001;
            
            else                                          sqrSel = 'b000000000;
        end
    end
    
    always @ (negedge   clk) begin
        if (prevIn != myIn && myIn != 0)
            Turn = ~Turn;
        prevIn = myIn;
        myIn = In;
    end
    
    // Win checking
    gameState state (
        .clk(clk), .Reset(Reset), .Cells(Cells), .winState(winState), .Color(Color)
    );
    
    // Video generator
    videoElements VGA (
        .clk(clk), .hsync(hsync), .vsync(vsync), .rgb(rgb), .Cells(Cells), .Color(Color)
    );
    
    // Board cells
    Cell cell1 (
        .clk(clk), .Sel(sqrSel[0]), .Turn(Turn), .Reset(Reset),
        .State(Cells[1:0])
    );
    
    Cell cell2 (
        .clk(clk), .Sel(sqrSel[1]), .Turn(Turn), .Reset(Reset),
        .State(Cells[3:2])
    );
    
    Cell cell3 (
        .clk(clk), .Sel(sqrSel[2]), .Turn(Turn), .Reset(Reset),
        .State(Cells[5:4])
    );
    
    
    Cell cell4 (
        .clk(clk), .Sel(sqrSel[3]), .Turn(Turn), .Reset(Reset),
        .State(Cells[7:6])
    );
    
    Cell cell5 (
        .clk(clk), .Sel(sqrSel[4]), .Turn(Turn), .Reset(Reset),
        .State(Cells[9:8])
    );
    
    Cell cell6 (
        .clk(clk), .Sel(sqrSel[5]), .Turn(Turn), .Reset(Reset),
        .State(Cells[11:10])
    );
    
    
    Cell cell7 (
        .clk(clk), .Sel(sqrSel[6]), .Turn(Turn), .Reset(Reset),
        .State(Cells[13:12])
    );
    
    Cell cell8 (
        .clk(clk), .Sel(sqrSel[7]), .Turn(Turn), .Reset(Reset),
        .State(Cells[15:14])
    );
    
    Cell cell9 (
        .clk(clk), .Sel(sqrSel[8]), .Turn(Turn), .Reset(Reset),
        .State(Cells[17:16])
    );
endmodule

Step 6: Constraints

The constraints file is a modified version of the one from embedded thoughts.com. A clock signal is routed into clk, the basys board's first 9 switches are mapped to In, RGB, hsync and vsync are mapped to VGA, and winState is mapped to the first LED. All these interface with gameLogic, mind you.

# Clock signal
# Credit to embeddedthoughts.com
# https://embeddedthoughts.com/2016/07/29/driving-a-vga-monitor-using-an-fpga/
set_property PACKAGE_PIN W5 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]
 
# Switches
set_property PACKAGE_PIN V17 [get_ports {In[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[0]}]
set_property PACKAGE_PIN V16 [get_ports {In[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[1]}]
set_property PACKAGE_PIN W16 [get_ports {In[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[2]}]
set_property PACKAGE_PIN W17 [get_ports {In[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[3]}]
set_property PACKAGE_PIN W15 [get_ports {In[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[4]}]
set_property PACKAGE_PIN V15 [get_ports {In[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[5]}]
set_property PACKAGE_PIN W14 [get_ports {In[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[6]}]
set_property PACKAGE_PIN W13 [get_ports {In[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[7]}]
set_property PACKAGE_PIN V2 [get_ports {In[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {In[8]}]

set_property PACKAGE_PIN U16 [get_ports {winState}]
set_property IOSTANDARD LVCMOS33 [get_ports {winState}]

# Buttons
set_property PACKAGE_PIN U18 [get_ports {Reset}]
set_property IOSTANDARD LVCMOS33 [get_ports {Reset}]


#VGA Connector
set_property PACKAGE_PIN G19 [get_ports {rgb[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[8]}]
set_property PACKAGE_PIN H19 [get_ports {rgb[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[9]}]
set_property PACKAGE_PIN J19 [get_ports {rgb[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[10]}]
set_property PACKAGE_PIN N19 [get_ports {rgb[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[11]}]
set_property PACKAGE_PIN N18 [get_ports {rgb[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[0]}]
set_property PACKAGE_PIN L18 [get_ports {rgb[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[1]}]
set_property PACKAGE_PIN K18 [get_ports {rgb[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[2]}]
set_property PACKAGE_PIN J18 [get_ports {rgb[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[3]}]
set_property PACKAGE_PIN J17 [get_ports {rgb[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[4]}]
set_property PACKAGE_PIN H17 [get_ports {rgb[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[5]}]
set_property PACKAGE_PIN G17 [get_ports {rgb[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[6]}]
set_property PACKAGE_PIN D17 [get_ports {rgb[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rgb[7]}]
set_property PACKAGE_PIN P19 [get_ports hsync]
set_property IOSTANDARD LVCMOS33 [get_ports hsync]
set_property PACKAGE_PIN R19 [get_ports vsync]
set_property IOSTANDARD LVCMOS33 [get_ports vsync]


set_property PACKAGE_PIN U18 [get_ports reset]
        set_property IOSTANDARD LVCMOS33 [get_ports reset]

Step 7: Game Play

  1. The game starts out with an empty grid
  2. Players take turns flipping the first 9 switches on the Basys Board, one at a time
  3. Each time a switch is flipped, a turn is taken and the chosen cell is turned into an X or an O
  4. When a row of three matching cells is made (horizontal, vertical, diagonal), the cells are turned red and the game is won. No more moves can be made until the game is reset using the middle button on the control pad.

Watch the demo here