Раз уж в прошлой статье я начал рассказывать о принципах оцифровки и воспроизведения звука, то наверное можно и немного рассказать о самом проектировании на ПЛИС. Сегодня я покажу, как реализовать проект "с нуля" - безо всяких makefile и докеров - просто на запущенном Quartus Prime под Windows. Ну а поскольку последним проектом у меня была музыкальная шкатулка, то и продолжу я с той же ноты - сделаю мини-пиано для исполнения кусочка "в лесу родилась ёлочка".
Правда кнопки у меня на плате только 4, много не наиграешь...
Поскольку сегодня формат урока, то начнём с требуемых для его выполнения вещей. Урок ориентирован на Quartus, поэтому нужна плата с ПЛИС от фирмы Altera (выкуплена Intel-ом, так что новые уже идут под Intel-овским брендом). Продвигаемые самим Intel платы стоят как туалет на МКС, поэтому обратимся к китайцам. А китайцы в 2020 году внезапно решили, что на любой уважающей себя плате с ПЛИС обязательно должен быть RS-232 порт 🤦♂️ Вот тольку куда его пихать сегодня... И ладно бы поставили, так они его ставят вместо usb-uart интерфейсов, что совсем огорчает. Зачем они это сделали - непонятно, но если закрыть глаза на него, то вот эта плата выглядит неплохой:
У неё на борту разъём D-SUB (VGA) для подключения монитора, 7-сегментный индикатор, 8мб оперативки, 1кб eeprom (читай flash) и 2 мегабайта flash-памяти на конфигурационном хранилище (которое и как флешку тоже можно использовать), кнопочки и светодиодики. Главное - в комплекте идёт USB Blaster. Без него плату с компьютером вам не подружить (COM-порт на плате нельзя использовать для прошивки)
Более достойным вариантом я считаю вот эту плату.
Самое главное преимущество - куда как более жирный ПЛИС. на 15к логических элементов вместо 6к. Это значит, что проекты вы на нём сумеете запускать куда как более сложные. В 2 раза больший объём встроенной в ПЛИС оперативки: 504 кбит вместо 273 кбит. Количество ног в 4 раза больше. Так же на плате есть D-SUB, 7-сегментник (но только 3 разряда), тоже кнопочки и тоже светодиодики, 8мб флешки (теперь флешка одна на всё), целых 32 мб оперативки, гигабитный Ethernet! и usb-uart преобразователь. В целом - на порядок лучше предыдущей, хоть в комплекте и не идёт USB Blaster, нужно покупать отдельно.
Всё что я указывал - это ценовой диапазон "до 3 000 руб комплект" и с хорошей периферией на борту. Но Aliexpress в помощь, можно найти и вот такой вариант с новеньким Intel (уже Intel) Cyclone 10 на 16к логических элементов и модным HDMI . Или вот такая - пусть и не самая передовая, зато по-настоящему богатая на периферию. В целом - на любой вкус и цвет - такой он алик. Можно даже взять эконом-вариант: CPLD платку. Но нужно понимать, что тут всего 240 логических элементов и ни единого байта встроенной оперативки. Для примера - на всех остальных как минимум 6 000 логических элементов.
Сам я когда-то купил вот такую платку
Хоть и не всё на ней продумано, но в целом устраивает. Сегодня таких уже не найти.
Кстати, разъём VGA на плате, несмотря на своё устаревание, обладает определёнными преимуществами по сравнению с HDMI. Так как для работы HDMI нужна более высокая частота, то ни о каком Full HD можете и не мечтать - тактировать HDMI понадобилось бы с частотой в пару гигагерц, чего ПЛИС физически не может выполнить. Придётся довольствоваться 640x480. А VGA в той реализации, которая идёт на платах - управляется параллельно. Требуется целых 18 ног от ПЛИС, но заметно более низкая частота - справиться с Full HD уже реально.
Хватит о железках, пора качать Quartus. Если у вас Cyclone IV или новее, то качайте просто самую последнюю Lite версию. Она бесплатна. А вот с более старыми циклонами, придётся качать и более старый квартус. К примеру у меня валяется вот такая платка. Для неё идёт только Quartus 13 и старше. Даже Quartus 13.1 уже не подходит.
Так же приходится качать 13-й квартус и мне с моим USB Blaster-ом. Возможно китайцы сегодня пихают в них более новые прошивки, но мой на Windows напрочь отказывается дружить с драйверами, идущими в комплекте новых Quartus-ов. Хуже того, сочетание нового драйвера с этим USB Blaster-ом приводит к экранам смерти... Забавненько. Хотя мне от старого Quartus-а нужен только лишь драйвер из комплекта. Этот старый драйвер дружит и с виндой 10-кой (подписан, сертифицирован, все дела), и с новыми Quartus-ами и с моим USB Blaster-ом. Он идеален.
Но так как Quartus - штука проприетарная, то даже скачка превращается в квест. Обязательно надо зарегится. Без этого никак. Перейти на страницу скачки и выбрать последнюю версию в Lite исполнении. Качать всё не обязательно. Достаточно скачать сам Quartus и пак, содержащий ваш ПЛИС. Для меня это Cyclone IV device support. Если пака с вашим ПЛИС нет, то смотрите более старые версии Quartus.
Запускаем Quartus Prime. Если у вас Windows или Linux, то это должно вам даться безо всяких усилий. Разные хитрости только на macOS нужны.
Как только загрузиться главное окно Quartus, нажимаем Ctrl+N. В открывшемся списке выбираете New Quartus Prime Project.
Появится окно, на котором нужно просто нажать Next
Далее нужно указать, что создавать. В первом поле выбираете папку. И тут внимание - Quartus будет сохранять файлы прямо в эту папку, так что укажите пустую, предназначенную для этого конкретного проекта,
Имя проекта (второе поле) и объекта верхнего уровня (третье) пусть будет top
На следующей вкладке оставляете Empty project, и сразу Next, на очередной вам предложат добавить файлы, которых у вас нет, так что снова Next. Так мы и пришли к окну выбора вашего FPGA. На мой взгляд, самый простой способ - это ввести название своего камня в поле
Name filter.
Вводить достаточно до тех пор, покуда не увидите нужное в списке Available devices. Как увидите - выбирайте и жмите сразу Finish, так как ничего интересного больше не будет. Проект готов, нужно добавлять файлы.
Ещё раз нажмите на Ctrl+N. Но теперь выбирайте
Verilog HDL File
И жмите OK.
Открывается новый пустой файл в редакторе, в котором нужно что-то писать.
Вот теперь и начинается тот самый этап, из-за которого я откладывал изучение FPGA несколько лет. Проектирование. И главное, что должен для себя уяснить каждый программист - тут проектируется не программа, а микросхема! При декомпозиции нужно думать, что вы выделяете именно микросхему, а не блок кода. Для программера со стажем это не всегда просто.
Задача данного проекта - выводить на ногу ПЛИС меандр с определённой частотой. Откуда взять - использовать сигнал с присутствующего на платке кварцевого генератора. А генератор присутствует на любой платке ПЛИС. Даже на упомянутой выше CPLD с 240 логическими элементами. Но генератор этот генерирует сигнал высокочастотный: как правило 48 или 50 мгц. Это несколько выше слухового диапазона. Соответственно частоту меандра надо делить.
Делается это при помощи счётчика, который каждый такт входного сигнала увеличивает своё значение на 1. Как только он достигает своего максимального значения, счётчик сбрасывается а выходная ножка меняет своё состояние на противоположенное. Значит, выбирая различные максимальные значения для счётчика, можно получить различные коэффициенты деления. А следовательно - разную частоту на выходе. Это именно то что надо. Такой счётчик-делитель уже хорошо годиться на выделение в отдельный модуль.
Запомнили и думаем дальше - нам нужно поддержать 4 разные кнопки, и при нажатии на каждую из них должна воспроизводиться своя нота. Сделать это можно 2-мя способами:
1. Держать целых 4 делителя, каждый выводит сигнал определённой частоты. При нажатии кнопки, коммутировать один из делителей с выходом схемы.
2. Использовать 1 делитель с управляемым максимальным числом. При нажатии на одну из кнопок - подавать на его вход значение, соответствующее этой кнопке. Также делитель будет работать не всё время, а при установленном сигнале включения (на схеме - en)
Останавливаемся на 2-м варианте - он выглядит проще. Отсюда выясняется, что у задуманной изначально пищалки должен быть вход, на котором будет указываться максимальное значение счётчика и сигнал включения. А кроме того нужен ещё один модуль, который будет формировать это значение, исходя из нажатой кнопки.
Итого: 2 блока: управляемая пищалка + блок управления. Поехали.
Начнём писать с "сердца" пищалки - делителя частоты. Работать он будет просто - отсчитывать такты до указанного. А как досчитал - инвертировать значение своего выхода и начинать считать заново. То есть вот такой вот код
Как мне кажется, в комментариях этот код не нуждается, ну за исключением объяснения - что такое ctr, tone и что такое q.
Первый - это сам счётчик, объявленный вот так:
Второй - входной параметр модуля:
ну а q - это выход модуля в виде регистра
Последнее, что требуется модулю - это отключаться, хотя бы временами. Поэтому вводим входящий сигнал
И несколько дополняем код
Тут на месте троеточия - уже указанный сверху код. В данном случае без установленного enable, модуль будет просто выдавать константную "1" на выходе q.
Остаётся указать - когда именно выполнять все эти действия - разумеется по синхросигналу. А раз так, то в модуль надо провести этот синхросигнал
И поместить весь готовый код внутрь блока always
Вот теперь модуль готов и может снижать входную частоту clk до какой угодно требуемой. Снижать он её будет в (tone + 1) * 2 раз. Откуда я нарыл такую кривую формулу?
А давайте смотреть. Модуль ведёт отсчёт ctr от 0 до указанного в tone числа. Если в tone записано число 3, то считать будет так.
0, 1, 2, 3
Заметили - тут не 3, а 4 числа. Ещё и 0. Именно поэтому и tone + 1. А теперь что происходит при переходе с tone на 0? Происходит инверсия выхода модуля. C 0 на 1 или с 1 на 0. Но полный такт - это две инверсии: сначала с 0 на 1, а затем с 1 на 0. Именно поэтому деление частоты и происходит в (tone + 1) * 2 раз.
Полный код модуля beeper.v:
Не забываем сохранить файл, и теперь можно создать ещё один. Это и есть управляющий модуль, ответственный за выбор значения tone, используемого в модуле beeper. Назовём его selector.
Помним конечную цель - хотим проиграть кусочек от "в лесу родилась ёлочка" - выбираем ноты. Ими оказываются:
ДО, ФА, СОЛЬ, ЛЯ первой октавы.
В википедии смотрим их частоту, округлённом её до целых и записываем.
В то время, как значение счётчика beeper-а, ровно как и состояние его входа нужно хранить и помнить, то параметр tone можно определить по состоянию нажатия кнопок. Для него не нужен синхросигнал. Само изменение входных значений является триггером подобного изменения. А раз не нужен, то в списке входов модуля его не будет. Нужны на вход кнопки, а на выходе будет устанавливаться значение для tone. Вот такой вот код:
Тут всё предельно просто: в зависимости от нажатой кнопки, выбирается одно из предопределённых значений. При этом учитывается и вариант, что было нажато несколько кнопок одновременно - приоритет даётся более высокой ноте.
И тут опять приходится делать выбор, немного не логичный для программиста (хотя он вполне логичен для ФП). Модуль просто обязан выдавать что-то на выход, вне зависимости от того, будет ли кто-то читать его выдачу или нет. А значит tone должен присваиваться в каждой ветке if else. Можно было бы сделать последний else блок, в котором будет задаваться какое-то специально значение. Но зачем? Всё равно, без нажатой кнопки будет останавливаться сама пищалка. Так что делитель ноты ДО будет выводиться всегда, покуда не нажата одна из кнопок: фа, соль, ля.
Осталось понять - что же за зверь такой - выражение TONE_A4[WIDTH-1:0]. И с ним всё тоже совсем не сложно. DIV_A4 - это константа - делитель частоты, настроенный для генерации звука ноты ЛЯ 1-й октавы (не спрашивайте только, почему её ноты имеют маркировку с цифрой 4) А выражение со скобками обозначает, что мы используем не всю константу, а лишь несколько её бит. Количество этих бит задаётся константой WIDTH. Само выражение понадобилось для того, чтобы Quartus не выдавал на данной строке предупреждение о обрезании константы TONE_A4 (которая по-умолчанию 32 бита) до 17 бит, требуемых для делителя.
Ну и да - блок always @(*) - это блок для блокирующего присваивания. Поскольку в данном блоке не задан синхросигнал, то он просто создаст комбинаторную схему безо всяких регистров, наподобие шифратора и мультиплексора, как в нарисованной мной схеме.
Так - многое прояснилось, кроме самих констант TONE_x4. Задаются они так:
Тут INPUT - это частота кварцевого генератора платы:
FREQ_A4 - это частота ноты ЛЯ 1-й октавы в герцах.
ну и зловещие / 2 - 1 - это ни что иное, как следствие уже упомянутой формулы (tone + 1) * 2. Простая математика.
Если div = (tone + 1) * 2, то tone = div / 2 - 1
Ну и всё, остаётся только записать модуль selector.v полностью:
Последним будет модуль top, существование которого мы указывали на 2-м шаге создания проекта. Его принято делать простым, чтобы он только лишь соединял другие модули, не вводя новых регистров и always-блоков.
Тут вводится-таки точное значение частоты кварцевого генератора и на основании его считается необходимое для счётчика beeper-а и значения tone количество бит WIDTH. Для этого берётся максимальное число, которое в нашей прошивке мы можем запихнуть в tone. Это константа для ноты ДО. Вот её и считаем, а после используем встроенную в Verilog функцию $clog2 для подсчёта необходимого количества бит. Ну и всё, результатом будет модуль top.v:
Теперь нужно скомпилировать это всё нажатием на Ctrl+L.
Скомпилироваться оно скомпилируется, но предупреждений накидает много. И самые критические для нас - это отсутствия привязки к конкретным кнопкам! Так что давайте нажмём Ctrl+Shift+N, открыв тем самым Pin Planner:
В окне Pin Planner-а много полей и возможностей, но нас будет интересовать исключительно таблица внизу. И ещё - описание или схема вашей конкретной платы. На ней вы должны будете найти такие вещи:
- номер ноги, к которой подключён кварцевый генератор
- номера ног, к которым подключены кнопки
- номер ноги, к которой подключена пищалка
Если пищалки на плате нет (и если кнопок нет), то выбираете выводы платы для присоединения данного оборудования.
Теперь выбранные пины нужно записать в столбец Location таблицы. Можете выбирать ногу из списка, до остервенения крутя скролл... Но мне больше по душе просто вбить текст PIN_xx в это поле, где xx - выбранный номер пина.
Всё выбрали, окно можно закрывать и заново жать Ctrl+L. Теперь количество предупреждений должно поубавиться. Да и работать схема уже должна начать. Зальём, чтоли. Ищем на тулбаре такую вот кнопку
Нажимаем и получаем такое окно:
По идее, всё в этом окне должно быть заполнено правильно. На всякий случай проверьте, что сверху рядом с кнопкой Hardware Setup... написано USB-Blaster [USB-0], выбран режим JTAG. А в нижнем окне под нарисованной микросхемой подписан именно ваш камень. Если всё так, то жмите Start.
Если не написано USB-Blaster [USB-0], то видимо вы не подключили этот чудный девайс к своему компу, или не установили драйвера (или быть может для китайского USB Blaster-а неплохо бы взять драйвера от Quartus 13, те обязательно подойдут).
Теперь уже почти всё. Вот только в логе страшные критические предупреждения с не менее страшным сообщением:
Critical Warning: Timing requirements not met
В моей статье про таймер это предупреждение знаменовало неработоспособность всей схемы. А тут работает: кнопки жмуться, ноты проигрываются. Но всё-таки надо объяснить Quartus-у хотя бы какой у нас кварц. Опять Ctrl+N, но на этот раз уже Synopsys Design Constraints File.
В новом файле пишем
И сохраняем как top.sdc
После этого предупреждения исчезают и при компиляции остаётся лишь одно предупреждение о том, что мы не использовали в дизайне выводы, подключённые к конфигурационной флэшке. Но мы и не должны.
А теперь о том, что же такое мы написали в top.sdc.
derive_clock_uncertainty вводит тактовую неопределённость. И я понятия не имею, что это. Но без неё Quartus будет недоволен.
create_clock указывает, что к порту clk подключен кварцевый генератор, с частотой 48mhz.
А далее мы сообщаем Quartus-у при помощи set_false_path, что нам совершенно не интересно, укладываются ли входы и выходы нашей схемы в какие-то временные ограничения. Собственно мы что и сделали - перечислили все входы и выходы.
Всё, осталось сыграть мелодию!
И да - качество аудиосистемы у меня неуклонно повышается. От пищалки на плате, к jbl-овским колонкам. Хмм... может так и до аудиофильной аппаратуры дойду? Хотя у меня такой нет.
Я не являюсь профессионалом в написании руководств и учебников, поэтому крайне нуждаюсь в обратной связи по качеству материала. Если что-то написано непонятно, что то не до конца мной разъяснено или избыточно разжёванно, прошу написать об этом в комментариях. Если всё хорошо и полезно - тоже пишите или просто поставьте лайк.
В следующей статье мы продолжим работу над проектом: перенесём на VisualStudio Code, добавим autocomplete, подчёркивание ошибок в редакторе, сборку и заливку в одну кнопку и главное - добавим тестирующий наши модули код. Прошу: