Создание игры ColorFlood на ПЛИС: от теории к «железу»
На профильных ресурсах, включая SE7EN, предостаточно материалов о разработке для ПЛИС. Однако большинство из них сосредоточено на сугубо утилитарных задачах: реализации вычислительных блоков, интерфейсов связи или алгоритмов ЦОС. Это, безусловно, важно, но как насчет игровых проектов? Речь пойдет не об эмуляции старых консолей, а о создании игровой логики «с нуля» на языках описания аппаратуры, таких как Verilog или SystemVerilog.
Моя первая попытка реализовать нечто подобное — проект DoodleJump — столкнулась с суровой реальностью. Чем сложнее механика, тем более громоздкой становится архитектура. В итоге я уперся в дефицит блочной памяти (BRAM) и избыточную асинхронность кода при попытке завести его на частоте 100 МГц. Проект пришлось заморозить, но идея создания полноценной игры на FPGA не давала покоя.
Архивные материалы: ранние этапы разработки (DoodleJump)



Формулировка концепции
Анализируя прошлые ошибки, я выработал критерии для нового проекта:
- Аппаратная независимость: использование чистого Verilog/SystemVerilog без привязки к вендорским IP-ядрам.
- Глубина геймплея: проект должен быть интереснее классического Pong.
- Вариативность: наличие нескольких режимов сложности и типов игры.
- Оптимизация: минимальное потребление ресурсов для запуска на бюджетных чипах.
Идеальным кандидатом стала игра ColorFlood. Её лаконичная графика и логика отлично ложатся на архитектуру ПЛИС. Ниже представлена демонстрация игрового процесса в режиме «Человек против ПЛИС»:
Посмотреть демонстрацию на YouTube
Разработка велась на базе платы Nexys A7 (Artix-7). Исходный код доступен в репозитории: GitHub: fpga_color_flood.
Техническая реализация

Ключевая проблема при выводе графики — отсутствие полноценного фреймбуфера в BRAM из-за его прожорливости. При разрешении 640х480 и цвете RGB444 классический массив пикселей не поместится в большинство ПЛИС. Решение пришло через дискретизацию: экран разбит на блоки 32х32 пикселя. Это позволило оперировать сеткой 20х15, что радикально снизило требования к памяти.
Управление реализовано интуитивно: сбоку от игрового поля находится панель выбора цвета. Кнопки навигации позволяют циклично переключаться между вариантами, а кнопка подтверждения совершает ход. Также добавлен визуальный прогресс-бар в нижней части экрана, отображающий степень захвата поля каждым игроком.
Игровые режимы
- Одиночный: классическая головоломка на захват поля за минимальное число ходов.
- VS ПЛИС: соревнование с алгоритмом. Реализовано два уровня сложности AI: от простого выбора первого доступного цвета до поиска наиболее выгодного хода на основе анализа соседних ячеек.
- PvP (Два игрока): поочередные ходы для двух человек.
Для генерации игрового поля используется массив из 128 псевдослучайных значений. Уникальность каждой партии обеспечивается смещением индекса в массиве, которое зависит от момента нажатия кнопки сброса (reset).
Настройка модуля game.sv
Для удобства адаптации под разные платы в основном модуле предусмотрены параметры:
- BG_COLOR: цвет фона.
- COLOR_1…5: палитра игровых блоков (совместима с RGB111).
- ANTI_BOUNCE_DELAY: настройка антидребезга кнопок (актуально для старых плат).
- DRAW_MARK: включение меток в центре квадратов для удобства подсчета.
- NEW_YEAR: пасхалка с праздничной елкой.
- ONLY_GAME_MODE_0: режим жесткой экономии ресурсов (отключает AI и PvP, снижая потребление до 5к LUT).
Пример визуализации с метками и праздничным режимом


Портирование на другие платформы
Если у вас не Nexys A7, для запуска потребуется создать обертку для файла game.sv:
- Настройте VGA-выход (или HDMI через конвертер типа
rgb2dvi). - Подайте тактовый сигнал 25 МГц (стандарт для 640×480 @ 60Hz).
- Назначьте кнопки управления (Select Up/Down, OK) и сигнал Reset (активный низкий уровень).
- Установите режим игры через двухбитный переключатель
mode_game.
Инструментарий: Скрипт на Python для конвертации графики
Этот скрипт преобразует любое изображение 32х32 в массив SystemVerilog для использования в игре.
from PIL import Image
import numpy as np
def convert_to_rgb444(r, g, b):
return ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4)
def image_to_sv(image_path, output_path):
img = Image.open(image_path).convert('RGB')
w, h = img.size
pixels = img.load()
with open(output_path, 'w') as f:
f.write(f"reg [11:0] image_array [{h-1}:0][{w-1}:0];\ninitial begin\n")
for y in range(h):
for x in range(w):
r, g, b = pixels[x, y]
val = convert_to_rgb444(r, g, b)
f.write(f" image_array[{y}][{x}] = 12'h{val:03X};\n")
f.write("end")
if __name__ == "__main__":
image_to_sv("icon.png", "image_init.sv")
Итоги
Проект показал, что даже без использования внешней памяти и сложных IP-ядер на ПЛИС можно реализовать полноценную игру с ИИ и графическим интерфейсом. Полная сборка со всеми функциями занимает около 11к LUT на Artix-7, что позволяет запустить её на большинстве современных отладочных плат.

Буду рад обратной связи и результатам тестов на ваших железных платформах. Ссылка на исходники: GitHub.



