Back to projects

06 / FPGA · VHDL

FPGA 2048

Course: Digital Logic Discipline: FPGA design in VHDL Date: 2024

The complete 2048 game implemented entirely in VHDL on an iCE40 FPGA, with a custom VGA controller, a tile-ROM graphics pipeline, and a classic NES-controller input.

Overview

For the final project of my Digital Logic course, I collaborated with a small group to implement the popular 2048 game entirely in VHDL on an iCE40 FPGA development board. The game outputs full VGA graphics and supports user input via a classic NES controller, enabling a complete gaming experience on custom hardware.

2048 game running on the FPGA hardware, rendered on a VGA monitor
The finished game on a VGA monitor, driven by the iCE40 board.
FPGA 2048 game system block diagram
System block diagram: VGA controller, game logic, and input processing modules.

Game logic & graphics pipeline

At the core of the project was the game logic, which required careful state management to handle tile movement, merging, and score updates. On top of that, I developed a custom graphics pipeline to render the board and tiles on a VGA display under strict timing requirements for synchronization signals. Each tile was stored in its own ROM block for efficient fetch and rendering during gameplay. The graphics system also required tightly timed pixel-clock generation, horizontal-sync, and vertical-sync signals to maintain a stable display.

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity vga is
    port(
        clk : in std_logic;
        HSYNC : out std_logic;
        VSYNC : out std_logic;
        rowOut : out unsigned(9 downto 0);
        colOut : out unsigned(9 downto 0);
        valid : out std_logic
    );
    end;
The VGA controller owns the sync signals and hands the rest of the design a pixel coordinate, plus a valid flag saying when that coordinate is inside the drawable region.
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity twoBlockROM is
    port(
        clk : in std_logic;
        rowIn : in unsigned(9 downto 0);
        colIn : in unsigned(9 downto 0);
        rowOffset : in unsigned (9 downto 0);
        colOffset : in unsigned (9 downto 0);
        rgbOut : out std_logic_vector(5 downto 0)
    );
    end;
A tile ROM addressed by that pixel coordinate plus an offset, so one stored tile can be drawn anywhere on the board. It answers with a six-bit RGB word.