Поиграем на ПЛИС: как превратить verilog в настольную игру color flood

Поиграем на ПЛИС: как превратить Verilog в настольную игру

Введение

Разработка на ПЛИС традиционно ассоциируется с умножителями, сумматорами, мультиплексорами, интерфейсами, цифровой обработкой сигналов и прочими строго утилитарными задачами. Логика, шины, протоколы, тайминги — всё предсказуемо и «правильно». Но ведь ПЛИС — это всё та же программируемая логика, и она ничуть не запрещает заниматься чем‑то более «человеческим» — например, играми.

Речь пойдёт не про запуск готовых эмуляторов приставок, а про полноценную реализацию игры непосредственно на Verilog/SystemVerilog, с собственной архитектурой, логикой и видеовыводом. Цель — не просто сделать «игрушку ради галочки», а спроектировать работающую игру, которую реально можно собрать под популярную отладочную плату и запустить у себя дома.

От попытки сделать Doodle Jump к смене подхода

Долгое время хотелось реализовать на ПЛИС что‑то вроде Doodle Jump: вертикальный скроллинг, прыжки персонажа по платформам, простая аркада с наглядной физикой. Были уже проделаны подготовительные этапы:
- написан вывод по VGA,
- реализована работа с SDRAM,
- частично присутствовала логика отображения прыгающего персонажа по «зеленым квадратикам».

Однако чем глубже уходила реализация, тем очевиднее становились ограничения:
- код распухал до чудовищных размеров,
- нагрузка на BRAM росла до предела,
- архитектура обрастала асинхронными участками, с которыми становилось тяжело жить на целевой частоте 100 МГц,
- сопровождать и переносить такой проект на другие ПЛИС было бы крайне неудобно.

В итоге от Doodle Jump пришлось отказаться и отложить идею «на потом». Стало понятно: если хочется, чтобы игру действительно кто‑то запустил, её архитектура должна быть проще, компактнее и менее ресурсоёмкой.

Критерии «правильной» игры для ПЛИС

После неудачного опыта сформировались требования к игре, которую действительно есть смысл реализовывать на ПЛИС:

1. Независимость от конкретной ПЛИС.
Логика должна быть написана максимально «чисто» на Verilog/SystemVerilog, без завязки на специфичные IP‑ядра и закрытые модули производителя.

2. Игровая сложность выше уровня «теннис с одним мячиком».
Примитивный Pong с одной ракеткой и шариком — слишком просто и быстро наскучивает. Хотелось более содержательной механики, чтобы в игру реально хотелось играть.

3. Поддержка нескольких режимов игры.
Игра должна допускать изменение правил, параметров, сложности, чтобы запуск не сводился к однообразному сценарию.

4. Желательно минимальное потребление ресурсов.
Этот пункт не обязательно доводить до фанатизма, но логика должна хотя бы теоретически собираться под широкий спектр ПЛИС без тотального выжигания BRAM и логики. Полностью выполнить его не всегда возможно, но ориентир должен быть именно таким.

Выбор игры: почему именно Color Flood

Пару недель назад на глаза попалась мобильная игра Color Flood. Идея оказалась удивительно подходящей для ПЛИС:
- простая графика,
- работа с цветными квадратами,
- несколько режимов игры,
- понятная и ограниченная логика поля.

Смысл механики: поле разбито на квадраты разного цвета, игрок, выбирая цвет, «затапливает» область, постепенно захватывая всё больше пространства. Эта модель великолепно ложится на представление экрана в виде сетки блоков, а управление можно реализовать всего несколькими кнопками.

Архитектура видеовывода без BRAM‑фреймбуфера

Первичная задача — организовать фреймбуфер без использования встроенной блочной памяти (BRAM). Наивное решение — завести массив в лоб размером в каждый пиксель экрана. При минимально приличном разрешении 640×480 и формате цвета RGB444 это быстро превращается в неподъёмный по ресурсам вариант.

Ключевая идея — отказаться от покадрового хранения пикселей и перейти к блочному представлению экрана:

- весь экран делится на квадраты 32×32 пикселя;
- младшие 5 бит горизонтального и вертикального счётчиков отвечают за положение пикселя внутри квадрата (2^5 = 32);
- старшие разряды этих счётчиков используются как индексы в массиве, описывающем игровое поле.

При разрешении 640×480 пикселей деление на блоки 32×32 даёт сетку 20×15 квадратов. Уже такой массив (20×15 элементов) выглядит довольно разумным по объёму и прекрасно живёт в регистрах и простой памяти.

Рассматривались и другие варианты:
- 16×16 пикселей — слишком мелко, глаз устаёт,
- 8×8 — вообще становится трудно различать элементы.

В оригинальной мобильной игре минимальное поле — 12×12 квадратов, поэтому в аппаратной реализации остановились на поле 15×11 внутри общей сетки 20×15. Остальные квадраты используются под элементы интерфейса.

Организация интерфейса и управления

Поле расположено слева, что оставляет три свободных столбца справа или слева (в зависимости от разметки) под интерфейс. В среднем из этих столбцов размещаются квадраты доступных цветов, которые игрок может выбрать в свой ход.

Управление сделано максимально простым:
- одна кнопка переключает выбранный цвет (подсвечивая соответствующий квадрат контуром),
- вторая кнопка подтверждает выбор и запускает “заливку” поля.

Для удобства были реализованы два варианта кнопок для перемещения по списку цветов — вверх и вниз. Обе работают по кольцу: достигнут край — выбор переходит к противоположному концу списка. Это снижает раздражение от промахов и делает управление комфортнее.

Отказ от шрифтов и прогресс‑бар

Поддержка шрифтов и текстового вывода — отдельная задача: потребуются таблицы символов, дополнительная логика адресации и отрисовки, а это всё ресурсы и время. Для компактного проекта это лишнее.

Чтобы всё же показать игроку ход партии и не заставлять его «на глаз» считать захваченные квадраты, был реализован графический прогресс‑бар в нижней части экрана:

- область делится на три сегмента:
- левый — заполняется от края к центру прогрессом первого игрока,
- правый — от края к центру прогрессом второго игрока или ИИ,
- центральная серая область сужается по мере захвата поля.

В конце игры сегмент победившего игрока начинает мигать контуром — так визуально подчёркивается результат партии без единого символа текста.

Индикация активного игрока и декоративные элементы

Чтобы игрок не путался, чей сейчас ход, добавлена подсветка активного игрока: возле угла того, кто должен ходить, мигает небольшой квадрат‑индикатор. На статичных изображениях его может быть не видно, но в динамике это отлично работает и не требует сложной графики.

В качестве декоративной детали была реализована небольшая «ёлочка» — своеобразный новогодний бонус, подчёркивающий, что даже в жёстких рамках FPGA‑логики можно позволить себе немного праздничного оформления.

Режимы игры

Ценность проекта в том, что это не просто «одна жёстко зашитая партия». Предусмотрено несколько режимов игры (их количество можно расширять):

1. Игрок против игрока.
Оба управляют ходами с одних и тех же кнопок по очереди или с разных групп кнопок — всё зависит от разводки платы. Логика просто переключает активного игрока после каждого хода.

2. Игрок против простого ИИ.
Компьютер выбирает цвет по несложной эвристике: например, по максимальному немедленному приросту захваченной области или по предопределённой стратегии. Даже простой ИИ делает игру заметно интереснее.

3. Игра на ограниченное количество ходов.
Можно задать максимальное число ходов и посмотреть, кто захватит больше площади к финалу. Это даёт дополнительный соревновательный элемент.

4. Режим быстрой игры.
Уменьшается размер игрового поля или число цветов — партия проходит быстрее, а нагрузка на мозги ниже, удобно для демонстраций и обучения.

Структура кода при этом остаётся модульной: каждый режим — это по сути небольшое надстройка над общей игровой логикой, что облегчает дальнейшее развитие проекта.

Целевая плата и особенности переноса

Проект разрабатывался под отладочную плату класса Nexys A7 (совместимую с Nexys 4 DDR). Это популярная плата с VGA‑выходом, достаточным объёмом логики и приемлемым количеством встроенной памяти, что делает её удобной платформой для игровых экспериментов.

Однако из‑за минимизации использования BRAM и ориентации на «чистый» Verilog/SystemVerilog логика достаточно легко переносится на другие ПЛИС при условии:
- наличия видеоинтерфейса (чаще всего VGA или HDMI через внешний чип),
- хотя бы базового набора пользовательских кнопок и, по желанию, светодиодов,
- совпадения или адаптации частоты пиксельного тактового сигнала (для 640×480 обычно используется 25 МГц или около того).

Связка выглядит так:
- видеомодуль формирует сигналы синхронизации и координаты текущего пикселя,
- игровой модуль по этим координатам вычисляет цвет, используя массив квадратов‑тайлов,
- интерфейсный модуль обрабатывает кнопки и управляет состоянием игры.

Как запустить такую игру у себя

Чтобы повторить подобный проект или сделать свою вариацию, потребуется:

1. Плата с ПЛИС и VGA/HDMI‑выходом.
Подойдёт любая, где есть доступ к видеовыходу и пользовательским кнопкам.

2. Среда разработки от производителя ПЛИС.
Нужен стандартный набор: синтез, размещение‑маршрутизация, генерация битстрима.

3. Настроенный таймер/PLL для видеочастоты.
Для VGA 640×480 используется фиксированный пиксельный клок. Его можно получить из основного тактового генератора с помощью PLL/MMCM.

4. Проект на Verilog/SystemVerilog с разделением на модули:
- генератор видеосигнала (VGA‑контроллер),
- логика игрового поля (массив квадратов, их состояния и цвета),
- блок управления ходами и режимами,
- модуль интерфейса (кнопки, прогресс‑бар, подсветка активного игрока).

После синтеза и прошивки битстрима ПЛИС отображает на мониторе поле из цветных квадратов, а кнопки позволяют управлять ходом игры. При желании конфигурацию можно дорабатывать «на лету» в коде и пересобирать проект, экспериментируя с параметрами.

Оптимизация ресурсов и архитектуры

Отказ от полноразмерного фреймбуфера — ключ к низкому потреблению BRAM. Но это не единственное, что можно оптимизировать:

- Кодирование цветов можно сделать не в полном RGB444, а, например, в виде индексов палитры (несколько бит на цвет), а уже потом разворачивать индекс в реальный RGB внутри отдельного модуля.
- Массив квадратов хранить не как трёхкомпонентный цвет, а как номер цвета (3–4 бита), что ещё больше снижает объём памяти.
- Логику заливки можно реализовать как пошаговый процесс, растянутый на несколько тактов, вместо огромного комбинаторного блока, который сложно синтезируется и раздувает задержки.
- Игровые режимы и часть параметров (размер поля, количество цветов, ограничение ходов) удобно вынести в параметры модулей (parameters), чтобы собирать разные конфигурации из одного и того же исходника.

Куда развивать проект дальше

Игровая ПЛИС‑платформа — удобная точка старта для многих интересных экспериментов:

- добавить более «умный» ИИ с анализом потенциальных ходов наперёд;
- реализовать сохранение и загрузку состояния игры во внешнюю память;
- подключить джойстик или энкодер вместо кнопок;
- вывести информацию об игре не только графикой, но и через дополнительный текстовый слой;
- перейти с VGA на HDMI при помощи внешнего кодера и отрендерить игру на современном мониторе или телевизоре;
- встроить систему очков, уровней сложности, рекордных результатов с отображением прямо на экране.

Заключение

Игры на ПЛИС — это не «баловство вместо серьёзной работы», а отличный способ:
- потренироваться в построении архитектуры аппаратуры,
- попрактиковаться в видеовыводе и работе с таймингами,
- пощупать, как реализуются конечные автоматы, интерфейсы, буферы и алгоритмы в «железном» виде,
- и, наконец, получить живой, визуально понятный результат, который можно показать кому угодно без длинных объяснений.

Простой на вид проект на основе Color Flood демонстрирует, что даже с ограниченными ресурсами ПЛИС можно сделать полноценную игру: с несколькими режимами, удобным управлением, наглядным интерфейсом и минимальной зависимостью от конкретной платформы. А дальше остаётся лишь один вопрос: какую следующую игру вы реализуете на ПЛИС — и какую архитектуру для неё придумаете?

Прокрутить вверх