← Back to list

How to Build a Real-Time Whac-A-Mole Game on an FPGA?

A Verilog guide to Finite State Machines, timers, and creating pseudo-randomness with Linear Feedback Shift Registers (LFSRs).

Shahzaib Ahmed · 2025-11-16 09:19 · 0 claps · 6.2 min read
#digital-logic-design #finite-state-machine #whack-a-mole #fpga-xilinx #verilog-hdl
Open on Medium ↗

How to Build a Real-Time Whac-A-Mole Game on an FPGA?

A Verilog guide to Finite State Machines, timers, and creating pseudo-randomness with Linear Feedback Shift Registers (LFSRs).

A top-down view of the Nexys A7 FPGA development board

A top-down view of the Nexys A7 FPGA development board

Remember the frantic, light-up fun of an arcade “Whac-A-Mole” game? That test of pure reaction speed is a perfect project for an FPGA. Why? Because FPGAs are masters of precise, parallel timing, making them ideal for building real-time games where every nanosecond counts.

In this guide, we will build a “Whac-A-Mole” game on a Nexys A7 FPGA Board. A “mole” (an LED) will light up at a random-seeming interval, and you will have a fraction of a second to “hit” the corresponding button. We will even keep score and track your lives as well.

This article assumes you have a basic understanding of Verilog HDL and are familiar with creating a project in the Xilinx Vivado Design Suite. We will be focusing on the game’s architecture and the interesting logic behind it.

The Game’s Blueprint

Before we write any code, let’s define our game’s rules and components:

  1. The Moles & Hammers: We will use 5 of the Nexys A7’s LEDs (LD0 – L D4) as our “moles” and the 5 corresponding push-buttons (BTNC, BTNU, BTND, BTNL, BTNR) as our “hammers.”
  2. The Brain (FSM): A Finite State Machine will control the game’s flow, moving between states like “waiting for a mole,” “mole appears,” “player scored,” and “player missed.”
  3. How do we make the game unpredictable with the “Randomizer”? We are going to make a Linear Feedback Shift Register (LFSR). We will use this special circuit to make a long, fake-random string of numbers that will help us choose which mole to pop up and how long to wait.
  4. The Timers: We need two clocks:
  • A Wait Timer that counts down until a new mole shows up (to keep the player on their toes).
  • A Reaction Timer that gives the player a short amount of time (0.5 seconds) to hit the active mole.
  1. Scoring: We will use 5 more LEDs (LD5–LD9) to show the player’s score in binary (0–31) and 3 LEDs (LD10–LD12) to show how many lives they have left (0–7).

LFSR — The Magic of Fake Randomness:

You just use the rand function in software. We make the randomizer ourselves in hardware. The LFSR is a clever, efficient way to do this.

An LFSR is a shift register where the input bit is a product of some of the previous bits, tapped and XORed together. It generates a predictable, but very long, sequence of numbers that appears random.

We will build a simple 16-bit LFSR. This will generate a new 16-bit “random” number on every clock cycle. We can use the lower 3 bits to pick a mole (0–4) and the upper 8 bits to set the delay time.

Writing the Game Logic in Verilog

Let’s build our two main modules. First, the lfsr, and second, the main whac_a_mole game logic.

1. The LFSR Module (lfsr.v)

This module is small but powerful. It just shuffles its bits on every clock cycle to produce a new pseudo-random number.

module lfsr (
    input  wire clk,
    output reg [15:0] random_out
    );

    // 16-bit LFSR with taps at 16, 14, 13, 11
    wire feedback;
    assign feedback = random_out[15] ^ random_out[13] ^ random_out[12] ^ random_out[10];

    always @(posedge clk)
    begin
        // Start with a non-zero seed
        if (random_out == 0)
        begin
            random_out <= 16'hACE1; 
        end
        else
        begin
            // Shift all bits and add feedback to the LSB
            random_out <= {random_out[14:0], feedback};
        end
    end
endmodule

2. The Main Game Module (whac_a_mole.v)

This is the heart of our game. It contains the button debouncers, the FSM, all timers, and the game state (score/lives). This file is a little complex, so comments are included to explain each part.

module whac_a_mole (
    input  wire clk,
    input  wire btnC, btnU, btnD, btnL, btnR, // Our 5 "hammers"
    output wire [4:0] moles_leds,             // LD0-LD4 (The moles)
    output wire [4:0] score_leds,             // LD5-LD9 (Score in binary)
    output wire [2:0] lives_leds,             // LD10-LD12 (Lives in binary)
    output wire game_over_led                 // LD15 (Game Over)
    );

    // 1. RANDOM NUMBER GENERATOR
    wire [15:0] lfsr_out;
    lfsr u_lfsr (
        .clk(clk),
        .random_out(lfsr_out)
    );

    // Use LFSR output
    wire [2:0] random_mole_index = lfsr_out[2:0]; // Use 3 bits
    wire [7:0] random_wait_time  = lfsr_out[10:3]; // Use 8 bits for delay

    // 2. BUTTON DEBOUNCING 
    // We need 5 debouncers. This is repetitive but crucial.
    // (A more advanced design might use an array of modules)

    // Simple debouncer: 2-stage synchronizer and a counter
    // We will just show one, the other four are identical
    reg [1:0] sync_btnC;
    reg [19:0] count_btnC;
    reg  debounced_btnC;

    always @(posedge clk)
    begin
        sync_btnC <= {sync_btnC[0], btnC};
        if (sync_btnC[0] != sync_btnC[1]) 
        begin
            count_btnC <= 0;
        end
        else if (count_btnC < 20'd100000) // ~1ms
        begin
            count_btnC <= count_btnC + 1;
        end
        else
        begin
            debounced_btnC <= sync_btnC[1];
        end
    end

    // ... (Repeat the debouncer logic above for btnU, btnD, btnL, btnR) ...
    // ... (For brevity, we will assume debounced_btnU, debounced_btnD, 
    // debounced_btnL, debounced_btnR exist)

    // For this guide, we will create simple wires to continue
    wire debounced_btnU, debounced_btnD, debounced_btnL, debounced_btnR;
    // (This is a simplification; use the 5x debouncer logic)


    // 3. GAME STATE REGISTERS
    reg [4:0] score = 0;
    reg [2:0] lives = 3;
    reg [4:0] active_mole = 5'b0; // 1-hot: 00001, 00010, etc.
    reg [2:0] active_mole_index = 0; // 0, 1, 2, 3, or 4

    reg [27:0] reaction_timer = 0; // 28-bit for ~0.5 sec
    reg [27:0] wait_timer = 0;     // 28-bit for random delay

    localparam REACTION_TIME = 28'd50000000; // 0.5 sec


    // 4. GAME LOGIC FSM
    localparam STATE_WAIT_NEW_MOLE = 2'b00;
    localparam STATE_MOLE_ACTIVE = 2'b01;
    localparam STATE_HIT = 2'b10;
    localparam STATE_MISS = 2'b11;

    reg [1:0] state = STATE_WAIT_NEW_MOLE;

    // Check for a hit
    wire correct_hit;
    assign correct_hit = (debounced_btnC & active_mole[0]) |
                         (debounced_btnU & active_mole[1]) |
                         (debounced_btnD & active_mole[2]) |
                         (debounced_btnL & active_mole[3]) |
                         (debounced_btnR & active_mole[4]);

    // Check for a wrong hit
    wire wrong_hit;
    assign wrong_hit = (debounced_btnC & ~active_mole[0]) |
                       (debounced_btnU & ~active_mole[1]) |
                       (debounced_btnD & ~active_mole[2]) |
                       (debounced_btnL & ~active_mole[3]) |
                       (debounced_btnR & ~active_mole[4]);

    always @(posedge clk)
    begin
        if (lives == 0)
        begin
            // Game Over state - freeze everything
            state <= STATE_WAIT_NEW_MOLE;
            active_mole <= 5'b0;
        end
        else
        begin
            case (state)
                STATE_WAIT_NEW_MOLE:
                begin
                    active_mole <= 5'b0; // All moles off

                    // Wait for a random amount of time
                    if (wait_timer < (random_wait_time * 10000))
                    begin
                        wait_timer <= wait_timer + 1;
                    end
                    else
                    begin
                        wait_timer <= 0;
                        reaction_timer <= 0;

                        // Pick a new mole (0-4)
                        if (random_mole_index <= 4)
                        begin
                            active_mole_index <= random_mole_index;
                            active_mole <= (1 << random_mole_index); // 1-hot
                            state <= STATE_MOLE_ACTIVE;
                        end
                        // if index > 4, we just wait another cycle for a new number
                    end
                end

                STATE_MOLE_ACTIVE:
                begin
                    if (correct_hit)
                    begin
                        state <= STATE_HIT; // Player scored
                    end
                    else if (wrong_hit || (reaction_timer == REACTION_TIME))
                    begin
                        state <= STATE_MISS; // Player missed
                    end
                    else
                    begin
                        reaction_timer <= reaction_timer + 1;
                    end
                end

                STATE_HIT:
                begin
                    score <= score + 1;
                    state <= STATE_WAIT_NEW_MOLE; // Go wait for next mole
                end

                STATE_MISS:
                begin
                    lives <= lives - 1;
                    state <= STATE_WAIT_NEW_MOLE; // Go wait for next mole
                end
            endcase
        end
    end

    // 5. ASSIGN OUTPUTS
    assign moles_leds = active_mole;
    assign score_leds = score;
    assign lives_leds = lives;
    assign game_over_led = (lives == 0);
endmodule

Connecting to the Nexys A7 (XDC)

With the logic complete, we just need to create our XDC constraints file to map our Verilog ports to the physical pins on the Nexys A7.

(Note: These are for the Nexys A7–100T. You need to map as per your board’s pins)

# System Clock
set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [ get_ports clk ]
create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} [ get_ports clk ]

# Buttons ("Hammers")
set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [ get_ports btnC ];
set_property -dict {PACKAGE_PIN M18 IOSTANDARD LVCMOS33} [ get_ports btnU ];
set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [ get_ports btnD ];
set_property -dict {PACKAGE_PIN M17 IOSTANDARD LVCMOS33} [ get_ports btnL ];
set_property -dict {PACKAGE_PIN P17 IOSTANDARD LVCMOS33} [ get_ports btnR ];

# LEDs ("Moles" LD0-LD4)
set_property -dict {PACKAGE_PIN H17 IOSTANDARD LVCMOS33} [ get_ports {moles_leds[0]} ];
set_property -dict {PACKAGE_PIN K15 IOSTANDARD LVCMOS33} [ get_ports {moles_leds[1]} ];
set_property -dict {PACKAGE_PIN J13 IOSTANDARD LVCMOS33} [ get_ports {moles_leds[2]} ];
set_property -dict {PACKAGE_PIN N14 IOSTANDARD LVCMOS33} [ get_ports {moles_leds[3]} ];
set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [ get_ports {moles_leds[4]} ];

# LEDs ("Score" LD5-LD9)
set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33} [ get_ports {score_leds[0]} ];
set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [ get_ports {score_leds[1]} ];
set_property -dict {PACKAGE_PIN U16 IOSTANDARD LVCMOS33} [ get_ports {score_leds[2]} ];
set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [ get_ports {score_leds[3]} ];
set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [ get_ports {score_leds[4]} ];

# LEDs ("Lives" LD10-LD12)
set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [ get_ports {lives_leds[0]} ];
set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [ get_ports {lives_leds[1]} ];
set_property -dict {PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [ get_ports {lives_leds[2]} ];

# LED ("Game Over" LD15)
set_property -dict {PACKAGE_PIN V11 IOSTANDARD LVCMOS33} [ get_ports game_over_led ];

Powering On Your Arcade

You are all set. Now, just follow the standard flow in Vivado:

  1. Run Run Synthesis .
  2. Run Run Implementation.
  3. Run Generate Bitstream.
  4. Open Hardware Manager, connect your Nexys A7 board with your machine, and Program Device.

Your FPGA is now a fully functional reaction game. With the help of parallel timers, real-time I/O, pseudo-random number generation, and FSMs, you have successfully constructed an entire system.

Watch the LEDs light up and test your reflexes! 😉


메타데이터
post_id
c9daae29d843
slug
how-to-build-a-real-time-whac-a-mole-game-on-an-fpga-c9daae29d843
url
https://medium.com/@msashahzaib/how-to-build-a-real-time-whac-a-mole-game-on-an-fpga-c9daae29d843
canonical_url
https://medium.com/@msashahzaib/how-to-build-a-real-time-whac-a-mole-game-on-an-fpga-c9daae29d843
author_url
https://medium.com/@msashahzaib
status
ok
fetched_at
2026-08-30 11:14:21