Найти в Дзене
ПЛИСовод.

FPGA. Музыкальная шкатулка

Приветствую вас в моём блоге, посвящённому ПЛИС. Сегодня я решил освоить постоянную память Cyclone IV. Ну и сыграть "В лесу родилось ёлочка" на пищалке моей платы.
Начну я с Википедии и её страницы "Частоты настройки фортепиано". Тут меня заинтересовали частоты нот. Кроме них, понадобились мне сами ноты песенки.
Блог, посвящённый ПЛИС и программированию, а тут ТАКОЕ!
Ну а поскольку мои познания в

Приветствую вас в моём блоге, посвящённому ПЛИС. Сегодня я решил освоить постоянную память Cyclone IV. Ну и сыграть "В лесу родилось ёлочка" на пищалке моей платы.

Начну я с Википедии и её страницы "Частоты настройки фортепиано". Тут меня заинтересовали частоты нот. Кроме них, понадобились мне сами ноты песенки.

Блог, посвящённый ПЛИС и программированию, а тут ТАКОЕ!
Блог, посвящённый ПЛИС и программированию, а тут ТАКОЕ!

Ну а поскольку мои познания в нотной грамоте лишь на самую капельку не нулевые, то дополнительно я воспользовался статьёй для расшифровки.

Всё вот это перемешиваю при помощи питона и получаю на выходе файл:

song.txt

Тут старший бит обозначает тип команды: это задержка или нота. Если 1, то задержка, и исполнитель должен на её протяжении ничего не делать. Время задержки задаётся в 1/512 долях секунды.

Если же старший бит равен 0, то второй бит говорит о том, должен ли исполнитель нажать (==1) или отпустить (==0) клавишу. Всё остальное - это информация о самой ноте, где младшие 4 бита - это одна из

до, дo♯, ре, ре♯ ми, фа, фа♯, соль, соль♯, ля, ля♯, си

Следующие 3 бита - номер октавы, хотя тут понадобятся только 4-я и 5-я.

Когда я составлял подобный "формат", то вдохновение черпал в формате MIDI файлов. И пусть это всего-лишь вдохновение, но идея отдельно кодировать начало/конец проигрывания ноты и отдельно задержки - мне показалась здравой.

А значит и исполнитель должен быть эдаким "компьютером", способном адекватно реагировать на разнообразные виды команд. Ну, попробую спроектировать.

Не сразу, не с первой попытки, но вышло так.
Не сразу, не с первой попытки, но вышло так.

В данной схеме song - это блок ROM, не более того - вот он.

song.v

При инициализации он заполняется уже указанным мной ранее содержимым файла song.txt. Ну а дальше - команду по этому адресу модуль выдаёт на выход.

Дальше мне нужно формировать задержки. Это как раз и будет делать

delay.v

Внутри этого модуля сразу 2 счётчика: предварительный и основной. Предварительный на выходе даёт частоту в 512 герц, с которой уже работает основной. Модуль ожидает, что duration на входе не будет меняться на протяжении всего времени самой задержки, так что вышестоящий модуль должен гарантировать постоянство этого значения.

Ну и как всегда - его тетстбенч. Задержку отмеряет, на протяжении всего её времени устанавливая 1 на выходе active.

-3

Вторая задача - это генерировать звук. На данный момент генерировать буду только одну ноту, да и выдавать её будем меандром. Для её генерации буду использовать

beeper.v

Данный модуль уже несколько более сложен, чем в предыдущем выпуске моего блога. Его задача не просто проигрывать какой-то "какой получилось" звук, а воспроизводить конкретную ноту. И да - значит он должен знать о частоте нот! Вот именно по-этому я и начал свои действия с изучения данного материала. И по этому-же мне понадобился ещё один модуль ROM-памяти.

pitches.v

Кстати, он у меня исполнен сложнее, нежели song.v. И да, Quartus его даже может синтезировать как ROM. А вот в случае с song.v он этого сделать не мог, потому что выход должен быть регистром. Иначе Quartus не умеет. И да - сами знания о частоте нот у меня хранятся тут:

pitches.txt

Реальная частота ноты вычисляется так:

freq = 3000000 / (value + 1)

где freq - результирующая частота, а value - это значение из этого файла. Ну например, первая же строчка: b32a, в десятичном формате это число 45866. 3000000 / (45866 + 1) = 65.4 - что соответствует ноте ДО второй октавы. И beeper.v именно этого и ожидает.

А к входным значениям beeper никаких условий не выставляет. Полученное от pitches.v значение предделителя сохраняется за счёт сигнала enabled у pitches.v На него подаётся тот же строб on, который и запускает проигрывание звука, как только строб снимается, значение предделителя застывает на выходе pitches.

Ну ок, осталось привести приблизительный расчёт тестбенча, который использует ноту 0x7f. Если что - по таблице pitches - это 0. Ну и частота соответствующая: 3 mhz.

Модуль управляется сигналами on и off, которые включают и выключают звук ноты.
Модуль управляется сигналами on и off, которые включают и выключают звук ноты.

Осталось ещё 2 компонента, первый - это sequencer - тот самый "процессор", который выбирает команды и выполняет их

sequencer.v

И он безумно простой. Там даже ни одного регистра то и нет. Всё и так выполняют модули delay.v и beeper.v. Когда создавал его, аж прослезился от счастья ))) Выход active модуля delay.v выдаётся как сигнал busy, сообщающий внешнему модулю, что текущую команду менять не следует, пока сигнал не будет снят (вы же помните, что именно такое требование есть у модуля delay.v).

Последним рабочим (не факт что это правильно) модулем является

top.v

Он содержит в себе и модуль song.v, и sequencer.v и непосредственно счётчик текущей позиции в песенке. А дальше всё просто. Как только sequencer выставляет флаг busy, счётчик замирает до тех пор, пока сигнал busy не будет снят. В остальное время одна за другой выполняются команды.

А также он заведует кнопками и знает - проигрывается ли что-то в данный момент. Ну и специальная задача для грядущего видосика - он ещё и лампочками мигает в такт музыки. Как то так:

Всё сработало как надо, музыка вполне себе воспроизводится!

На самом деле всё... Почти всё. Ведь ПЛИС можно заставить делать то-же самое, но чуть по-лучше. Хотя для этого придётся прицепить к нему внешний динамик и ОООЧЕНЬ сильно заморочиться с прошивкой, но в результате получится вот так:

Да, пришлось использовать всё те-же ноты и записи самого настоящего пианино. Но как я это всё организовал - в следующей статье!