Делаем Тетрис Для Fpga

Всем привет!

Делаем тетрис для FPGA

В эти длинные новогодние выходные я задавался вопросом, как легко написать какую-нибудь простую игрушку на ПЛИС с выводом на дисплей и управлением с клавиатуры.

Так родилась еще одна реализация тетриса на ПЛИС : Яфпгатетрис .

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

далеко Можно сказать, для меня это новый опыт. Если вас интересует, как можно запустить игру без операционной системы, реализовав это на самом низком уровне, используя триггеры и комбинационную логику, добро пожаловать под кат.



О девушке

Нам нужно «что-то», где будет работать наша игра.

Один из самых простых способов — взять комплект для разработки, в котором есть FPGA и какая-нибудь периферия для ввода-вывода.

В моем распоряжении оказался платок от Терасич с названием DE1-SoC .



Делаем тетрис для FPGA



Делаем тетрис для FPGA

Ну что я могу сказать? Девкит, как девкит. Периферии много: нас будут интересовать разъемы от них ПС/2 И VGA .

Для обучения (в школах или университетах) в самый раз.

Для наших целей мы купили его просто для того, чтобы поиграть (и поучить студентов), а не для реализации каких-то наших «производственных» идей.

Если вы вдруг используете DE1-SoC (или подобные платы) в своих реальных устройствах (а не просто моргаете светодиодом), поделитесь в комментариях, будет интересно.

SoC в названии чипа означает, что чип содержит как обычную логику FPGA, так и процессор ARM. Забегая вперед, скажу, что для своей задачи я не использовал ни ARM, ни какой-либо программный процессор, поэтому вы можете запускать мой проект на своих платах с другими чипами FPGA. Если вам интересно прочитать о поднятии связки FPGA+ARM и какие бонусы от этого можно получить, советую обратиться статья мой коллега Дес333 .



Что мы хотим получить

В концепцию «Тетриса» можно вкладывать разное, поэтому я набросал примерную спецификацию того, чего я хотел добиться:
  • Стандартный набор фигурок.

    Их поведение должно быть максимально похоже на их обычное поведение.

  • Игра красочная.

    Каждая фигурка имеет свой цвет.

  • Цифры генерируются случайным образом с равномерным распределением.

  • Должно появиться окно, отображающее следующий рисунок.

  • Должна быть информация о состоянии игры: количество очков, количество удаленных линий, текущий уровень.

  • Очки начисляются по «прогрессивной» шкале: чем больше линий вы очистите за раз, тем больше очков получите.

  • Чем выше уровень, тем быстрее падают цифры.

  • «Конец игры» определяется правильно, и можно начинать новую игру.

  • Действия пользователя вводятся с клавиатуры (PS/2).

  • Статус поля и прочее отображается на обычном дисплее через VGA-интерфейс.



Схема проекта



Делаем тетрис для FPGA

Есть три основные части:
  • Пользовательский ввод. Получаем данные с клавиатуры и «влияем» на систему.

  • Всё, что связано с самой игрой.

    По сути, FSM (конечный автомат), который принимает «запросы» от игрока и «делает все»: генерирует новые фигуры, перемещает их, удаляет линии и так далее.

  • Отображение состояния игры.

    Рисуем на дисплее через интерфейс VGA.



ПС/2

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

Для приема команд с клавиатуры вам понадобится контроллер PS/2. я использовал здесь этот .

Если немного обратиться к теории, то для каждого ключа свой набор коды , который клавиатура отправляет при нажатии или отпускании.

Возьмем клавишу «Enter»:

  • Марка: 5А
  • Обрыв: F0, 5А.

Давайте посмотрим, как это выглядит внутри FPGA: Обычное нажатие клавиши:

Делаем тетрис для FPGA

Как мы видим, действительно:
  • Нажимаем клавишу, приходит 5А.

  • Отпускаем: приходит F0, за ним 5А.

  • Нажимаем еще раз: приходит 5А и так далее.

Если зажать клавишу, то получим следующее:

Делаем тетрис для FPGA

Команда 5А приходит с некоторой частотой.

Нам понадобится небольшой набор ключей:

  • обычные стрелки – для управления фигурой.

  • н - начать новую игру.

Для определения типа события можно обойтись сдвиговым регистром и немного примитивной логикой, а все остальные клавиши просто пропустить.

Для этой игры я решил, что мне достаточно обнаруживать события нажатия клавиш и игнорировать события отпускания клавиш.

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

Признаюсь, я боялась, что фигурки будут двигаться слишком необычно, но все обошлось.

Обнаружив интересующее нас «событие», мы помещаем его в FIFO, откуда его подберет «прикладная логика» игры.

Если у вас на плате нет PS/2, но есть какие-то клавиши или тумблеры, то достаточно будет написать логику, которая будет переводить нажатия этих кнопок в «события», и игра ничего не заметит. Этот контроллер позволяет подключить мышь, но я не пробовал.



Базовая игровая логика

С одной стороны, логика тривиальна и описывается следующим автоматом:

Делаем тетрис для FPGA

(Честно говоря, я не знаю, использует ли кто-нибудь «State Machine Viewer» в продакшене; если да, то поделитесь в комментариях, почему.

За время разработки для FPGA я открывал его пару раз, и только в рамках обучение).

FSM «общается» со следующими блоками/модулями:

  • gen_sys_event — таймер, отсчитывающий время, по истечении которого нужно автоматически переместить фигурку вниз.

  • gen_next_block — генератор новой фигуры.

  • check_move — проверка возможности выполнения текущего «хода».

  • tetris_stat - накопление «статистики».

  • user_input — читает событие, которое «создал» пользователь.

Все очень похоже на «обычную» реализацию тетриса, написанную на C++/Java/etc: на этих языках в качестве функций выступают различные модули.

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

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

Весь код написан на Verilog, а точнее, на SystemVerilog. С одной стороны, SystemVerilog гораздо более гибок, чем Verilog, но с другой стороны, это приводит к тому, что вы не ограничены, и вам хочется реализовывать все больше разных мелочей :).

Я упростил себе жизнь: текущее состояние поля хранится в регистрах (а не во внутренней памяти), и из-за этого (а также из-за того, что некоторые вещи сделаны не оптимально) генерируется много логики, и проект занимает много ресурсов (около 3,2к ALM из 32к).

Если двигаться по памяти, то некоторые действия придется делать последовательно (например, сдвиг всего поля вниз, когда нужно удалить заполненную строку).

Скорее всего переделывать использование памяти я не буду.

В целях тестирования собрал проект для плат DE0/DE1 (братья платы у меня есть, но с бюджетными чипами: у них меньше ресурсов и они «молодого поколения»): проект соответствует ресурсам.

Однако… Скрытый текст .

сразу из коробки работать не будет:

  • Квартус будет жаловаться на некоторые вещи в qsf файле, потому что.

    Я собирал за 14 квартал, где нет Циклона II/III. Ранние версии Quartus таких вещей не знают: вам придется вручную удалять эти строки в qsf-файле, а затем ставить те же галочки в графическом интерфейсе Quartus.

  • Не подходит по частоте: «основная» частота в этом проекте — 108 МГц (на ней работает сам main_game_logic и рендеринг на VGA).

    Забегая немного вперед, частота 108 МГц - ведь разрешение 1280х1024, если использовать 640х480, то будет частота 25 МГц, и она подойдет.

  • Возможно, потребуется перегенерировать мегафункции для PLL и FIFO, поскольку они созданы для Cyclone V.
  • Возможно, вывод дисплея придется немного отредактировать (выбрать разные цвета), потому что.

    На каждый цвет выделено всего четыре бита (насколько я понимаю), а не восемь, как в этой плате.



Дисплей Дисплей

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

В этом комплекте вывод на VGA сделан следующим образом:

Делаем тетрис для FPGA

Каждый удар VGA_CLK необходимо задать новые значения цвета в модели RGB, а затем ЦАП преобразует эти значения в необходимый уровень сигнала.

В качестве контроллера сигнала VGA я взял модуль из демо-примеров, доступных на сайте.

CD для этого кита.

Забавно, что есть понятие CD, но в комплекте с платой нет компакт-диска: нужно скачать архив из Интернета.

Terasic использует этот «контроллер» в других комплектах: его легко гуглят по названию «vga_time_generator».

Он удобен тем, что его можно настроить на любой режим работы (640х480, 800х600 и т.д.), а также тем, что он выдает координаты ( пиксель_х , пиксель_y ) текущего пикселя для отображения.

Наша задача сводится к подстановке нужных значений цвета в зависимости от этих координат.

Делаем тетрис для FPGA

Я решил, что 640х480 не очень хорошо смотрится на большом мониторе и перешёл на 1280х1024, просто передав нужные значения из стандартный .

Кроме того, мне пришлось изменить значение VGA_CLK : вместо 25,175 МГц стало 108 МГц.

Правда, я потом немного пожалел об этом, но красота требует жертв.

Давайте посмотрим, как отображать некоторые примитивные объекты.

Например:

  
  
   

`define RGB_BLACK 24'h00_00_00 `define RGB_ORANGE 24'hFF_A5_00 logic [23:0] vga_data; localparam START_X = 100; localparam START_Y = 100; localparam END_X = START_X + 200 - 1; localparam END_Y = START_Y + 300 - 1; always_comb begin vga_data = `RGB_BLACK; if( ( pixel_x >= START_X ) && ( pixel_x <= END_X ) && ( pixel_y >= START_Y ) && ( pixel_y <= END_Y ) ) vga_data = `RGB_ORANGE; end assign { r, g, b } = vga_data;

Появится оранжевый квадрат размером 200x300 пикселей с левым верхним углом, расположенным в точке (100, 100).

Или:

`define RGB_BLACK 24'h00_00_00 `define RGB_ORANGE 24'hFF_A5_00 logic [23:0] vga_data; localparam MSG_X = 56; localparam MSG_Y = 5; logic [0:MSG_Y-1][0:MSG_X-1] msg; assign msg[0] = 56'b10010011110010000010000001100000001001000110001110001110; assign msg[1] = 56'b10010010000010000010000010010000001001001001001001001001; assign msg[2] = 56'b11110011110010000010000010010000001111001111001111001111; assign msg[3] = 56'b10010010000010000010000010010000001001001001001001001110; assign msg[4] = 56'b10010011110011110011110001100000001001001001001110001001; logic [$clog2(MSG_X)-1:0] msg_pix_x; logic [$clog2(MSG_Y)-1:0] msg_pix_y; localparam START_MSG_X = 100; localparam START_MSG_Y = 100; localparam END_MSG_X = START_MSG_X + MSG_X - 1; localparam END_MSG_Y = START_MSG_Y + MSG_Y - 1; assign msg_pix_x = pixel_x - START_MSG_X; assign msg_pix_y = pixel_y - START_MSG_Y; always_comb begin vga_data = `RGB_BLACK; if( ( pixel_x >= START_MSG_X ) && ( pixel_x <= END_MSG_X ) && ( pixel_y >= START_MSG_Y ) && ( pixel_y <= END_MSG_Y ) ) begin if( msg[ msg_pix_y ][ msg_pix_x ] ) begin vga_data = `RGB_ORANGE; end end end assign { r, g, b } = vga_data;

Будет отображаться ПРИВЕТ, ХАБР шрифтом высотой 5 пикселей оранжевого цвета на черном фоне.

(Присмотритесь к единицам в массиве сообщение ).

Думаю, понятно, как можно нарисовать какие-то статичные сообщения или игровое поле.



Мы печатаем строки

Для отображения статистики (строки «Оценка», «Линии», «Уровень» и их значения) я решил пойти по «классическому» пути.

Вы можете это увидеть, например, здесь .

Допустим, какая-то логика уже определила, какой символ (читай, букву или цифру) мы хотим отобразить #прямо сейчас (в зависимости от пиксель_х , пиксель_y ).

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

"00000000", -- 0 "00000000", -- 1 "00010000", -- 2 * "00111000", -- 3 *** "01101100", -- 4 ** ** "11000110", -- 5 ** ** "11000110", -- 6 ** ** "11111110", -- 7 ******* "11000110", -- 8 ** ** "11000110", -- 9 ** ** "11000110", -- a ** ** "11000110", -- b ** ** "00000000", -- c "00000000", -- d "00000000", -- e "00000000", -- f

Во многих проектах (которые можно найти в сети) с VGA используется такая таблица ( Шрифт ПЗУ ), но они рассчитаны на дисплей 640х480: для 1280х1024 получается маловато, поэтому нужно подготовить аналогичную таблицу, но с «большим» шрифтом.

Мне в этом помогла утилита наивный .

На вход берет psf файл, на выходе текстовый файл с Х, в тех пикселях, которые нужно отрисовать.

Используя свой любимый язык (или слегка изменив вывод исходной программы), измените X на «1», а пробелы на «0» и добавьте заголовок, чтобы создать файл MIF (который затем используется для инициализации ПЗУ).

Самый крупный шрифт, который я нашел в своем формате psf, был 32х16, и в принципе для этой задачи его хватило, но хотелось бы сделать его немного крупнее.

Насколько я понимаю, ограничений нет и с помощью этой утилиты можно подготовить ROM с любыми символами (например, русскими буквами).

Однако для заголовка Яфпгатетрис и сообщения ИГРА ЗАКОНЧЕНА мне этот размер показался маленьким, и я решил отобразить эти сообщения так же, как строку ПРИВЕТ, ХАБР в примере выше.

Вопрос только в том, как подготовиться.

сообщение , потому что мне очень не хотелось делать это вручную.

На ум сразу пришло относительно простое велосипедное(?) решение:

  • Введите текст нужного шрифта и размера в Paint/GIMP.
  • Сохраняем его в формате PNG без сжатия и сглаживания.

  • Мы используем какую-то готовую библиотеку для чтения PNG-файла и для каждого пикселя выводим 0, если «цвет белый», 1, если «цвет черный».

Полученный набор нулей и единиц также можно записать в ПЗУ (конечно, в другое место, кроме шрифта).



Несколько фотографий

Пара фотографий из серии «в разработке»: Скрытый текст Мы научились рисовать поле: фигуры просто падают вниз и все одного цвета.



Делаем тетрис для FPGA

Добавлена статистика и разные цвета.

Цвета привлекают внимание :)

Делаем тетрис для FPGA

Ну а окончательный вариант - в начале статьи :) P.S. Честно говоря, не знаю, почему на дисплее такие "размазы" при фотосъемке, может быть я не включил какую-то настройку в корке VGA, или мне просто не повезло.



Полученные результаты

Источники: https://github.com/johan92/yafpgatetris Видео: Я постарался сделать проект максимально параметризуемым и логически разделил его на части, так что если вы хотите делать на основе моего проекта «гоночные игры», где вам придется уворачиваться от других машин или змеи, просто напишите свою main_game_logic и немного подкорректируйте вывод (при необходимости).

Разработка заняла около 5 дней, если считать «чистое время»: пришлось повозиться с переворачиванием фигуры (по сути, дважды переписать алгоритм), много времени ушло на подбор цветов, размеров, выравнивание и расположение сообщений.

Внутренний перфекционист всегда требовал, чтобы внутренний дизайнер что-то передвинул, что-то увеличил/уменьшил и т. д. Для себя я усвоил, что разработка GUI – это не мое дело) В итоге цвета для фигурок я взял из приложения Тетрис в Вконтакте.

Если вы купили комплект и хотите научиться разрабатывать для FPGA, я не рекомендую делать непосредственно тетрис в качестве вашего первого проекта.

Начните со светодиодов, индикаторов сегментов, часов и других классических вещей.

Когда вы пройдете это, вы сможете попробовать тетрис или какую-нибудь другую простую игру.

Надеюсь, мой проект поможет в этом начинании.

Спасибо за внимание! Если у вас есть вопросы, задавайте без колебаний.

Теги: #FPGA #plis #FPGA #plis #verilog #systemverilog #systemverilog #tetris #vga #PS2 #games #altera #altera #terasic #de1 #soc #KIT #devkit #devkit #programming #Разработка игр #FPGA

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.