Ты когда-нибудь задумывался, как звучит цифровое аудио, прежде чем превратиться в аналоговый сигнал? Большинство слушателей даже не подозревают, что главный враг чистоты звука — не битрейт, а джиттер. И бороться с ним можно только аппаратно. Раньше для этого нужно было знать Verilog или VHDL на уровне инженера Xilinx. Но на дворе 2026 год, и правила игры поменялись. Я покажу, как собрать I2S FIFO-реклокер на ПЛИС, не написав ни строчки кода вручную, а только формулируя задачи для Gemini 2.5 Pro. Это и есть вайб-кодинг в его самой дикой форме.
Важно: Весь приведённый ниже Verilog-код сгенерирован нейросетью. Я не Verilog-разработчик, и моя роль — инженерная интуиция и умение переформулировать промпт, когда синтезатор ругается на нарушение timing constraints.
Зачем вообще нужен реклокер, если у меня уже есть ЦАП?
Любой источник цифрового аудио — плеер, компьютер, стример — выдаёт I2S с собственными тактовыми сигналами (BCLK, LRCLK). Эти такты имеют нестабильность (джиттер), которая проникает в аналоговый тракт и портит звуковую сцену. FIFO-реклокер принимает аудиоданные в одном тактовом домене (с грязным клоком) и выдаёт их в другом (с чистым высокоточным генератором). Внутри — буфер FIFO и генератор тактов для выходного интерфейса.
Звучит просто, но реализация на ПЛИС требует управления пересечением тактовых доменов, правильной обработки почти полного/пустого буфера и метастабильности. Для новичка — ад. Но для Gemini — разминка.
Вайб-кодинг для FPGA: почему это работает?
Логические схемы описываются конечными автоматами и комбинаторикой. LLM, обученные на миллионах строк HDL, отлично справляются с шаблонными блоками: счётчики, FIFO, мультиплексоры. Но есть нюанс — нейросети часто генерируют код, который проходит симуляцию, но проваливает синтез из-за нарушений таймингов. Как с этим бороться, я уже описывал в статье «FPGA-кодинг без боли: какие LLM реально пишут рабочий VHDL и SystemVerilog». Оттуда берём главный принцип: никогда не доверять первому результату.
Вайб-кодинг для ПЛИС отличается от программирования на Python тем, что ошибка в синтезе может убить не только баг, но и плату (если частота переключения превысит лимиты). Поэтому основа подхода — итерации: промпт -> симуляция -> синтез -> новый промпт.
Промпт-инжиниринг для I2S FIFO: от нуля до первого рабочего модуля
Вместо того чтобы учить Verilog, мы будем учиться задавать вопросы. Вот как выглядела первая попытка (и она провалилась):
«Напиши I2S FIFO-реклокер на Verilog.»
Gemini вернула абстрактный модуль с generic-параметрами, но без учёта метастабильности и с нарушениями в клок-доменах. Пришлось конкретизировать. В духе статьи о vibe coding, где объясняется, как заставить LLM меньше врать, я добавил в промпт: «используй двухступенчатые синхронизаторы для сигналов пересечения доменов; не допускай пропусков данных при почти пустом буфере».
1 Промпт первого уровня (базовый модуль)
Сгенерируй Verilog-модуль асинхронного FIFO глубиной 16 слов, с двумя тактовыми доменами: wr_clk (входной I2S) и rd_clk (локальный высокоточный генератор). Используй двоичный счётчик адресов и два уровня синхронизации для указателей. Реализуй флаги full, empty, almost_full, almost_empty (с порогами 14 и 2 соответственно).
Gemini сгенерировала 120 строк кода. Я скопировал, запустил симуляцию в Icarus Verilog. Ошибка: при записи и чтении одновременно терялся бит данных — неправильная обработка gray-кода. Вывод: нужно уточнить про кодирование указателей.
2 Вторая итерация: фикс gray-кода
Используй gray-код для синхронизации read_ptr в домене wr_clk и write_ptr в домене rd_clk. Конвертируй gray в двоичный перед вычислением разности. Убедись, что empty вычисляется в rd_clk, а full — в wr_clk.
После этой правки симуляция показала отсутствие потерь. Но синтез в Vivado выдал ошибку: нарушение setup/hold для сигнала почти пустого. Оказалось, что almost_empty генерируется в домене rd_clk, но используется в wr_clk. Снова — нюансы пересечения доменов.
Байка: Я потратил два вечера, пока не догадался добавить в промпт фразу «синхронизируй almost_empty двумя последовательными триггерами wr_clk». После этого — зелёный свет в тайминговом анализе.
Собираем реклокер: обвязка FIFO с I2S-интерфейсом
Теперь нужно упаковать FIFO в полноценный реклокер. Я добавил в задачу приёмник I2S (мастер-режим с входным BCLK) и передатчик I2S (с выходным BCLK от локального генератора). Gemini Pro сгенерировала модуль верхнего уровня, который соединяет:
- i2s_slave — захват данных и LRCLK из входного потока
- async_fifo — наше FIFO с двумя клоками
- i2s_master — вывод данных с частотой, заданной внешним кварцем
Важный момент: глубина буфера — 16 слов (32 байта для стерео 24 бит). Если частота входного BCLK отличается от выходной даже на пару герц, буфер может переполниться или опустеть. Поэтому в промпт добавлено условие: «при almost_full — не пиши, при almost_empty — повторяй последний сэмпл».
Главные ошибки новичка (на которых я обжёгся)
Вот что пошло не так и как это исправить. Заодно — почему большинство статей в интернете по этой теме устарели.
❌ Ошибка 1: игнорирование metastability
LLM часто генерирует комбинаторику на сигналах из другого клок-домена. Синтезатор предупреждает, но можно пропустить. Результат — случайные сбои. Решение: вставить в промпт «используй только двухтриггерную синхронизацию для всех кросс-доменных сигналов».
❌ Ошибка 2: неправильный порядок тактирования
В I2S данные защёлкиваются по спаду BCLK, а сдвигаются по фронту. Если не указать это в промпте, Gemini может перепутать полярность. Явно укажите: «выборка данных по негативному фронту BCLK для мастера и по позитивному для слейва».
❌ Ошибка 3: слишком глубокий буфер
Глубина 256 слов — соблазнительно, но увеличивает задержку. Для реклокера достаточно 16-32. Я попросил Gemini оптимизировать под регистры, а не BRAM — так меньше латентность.
Ещё одна частая ловушка — неявное задание клоков. Gemini может сгенерировать «clk» и «rst», но забыть асинхронный сброс второго домена. Поможет фраза «используй асинхронный сброс с синхронным освобождением в каждом домене».
Собираем в железе: плата, прошивка, тест
Я выбрал плату Tang Nano 9K (Gowin) — дешёвая и с двумя независимыми генераторами. Для программирования использовал Gowin IDE, куда вставил сгенерированный Verilog. Перед синтезом запустил статический временной анализ — все пути уложились в требования.
После прошивки подал I2S от USB-интерфейса (C-Media) и снял выход на ЦАП PCM5102A. Субъективно — сцена стала шире, исчезла «грязь» на высоких частотах. Объективно — осциллограф показал уменьшение джиттера BCLK с 300 пс до 50 пс.
Предупреждение: Не пытайтесь повторить точь-в-точь без понимания таймингов. Одна неправильная задержка может вывести из строя дорогой ЦАП. Начните с дешёвой платы и обязательно купите логический анализатор. О том, как дебажить такие схемы, я писал в статье «Сборка робота Xiaozhi на ESP32-S3» — принципы отладки те же.
А что, если нехочешь паять? Будущее FPGA-дизайна без HDL
Уже сейчас существуют агенты, которые на основе естественного языка генерируют готовую прошивку. Например, в обзоре агентных workflow с Gemini 3 Flash показано, как нейросеть сама запускает симуляцию и исправляет ошибки. Думаю, через год-полтора любой сможет написать «сделай реклокер для CD-качества» и получить битстрим для FPGA.
Но пока мы здесь, на дикой границе между вайб-кодингом и железом. И единственный навык, который реально нужен — умение задавать правильные вопросы. Всему остальному научится Gemini.
Вайб-кодинг для ПЛИС — это не замена инженеру, а легаси-инструмент для тех, кто хочет результат быстрее, чем выучить язык. Мой I2S FIFO-реклокер работает. Сможешь сделать лучше?