Блог
Собирающая битстрим CI-задача была красной одиннадцать дней. Под этим — тринадцать слоёв дефектов, каждый невидим, пока не вылечен верхний: фальшивая база кристалла, список зависимостей, который никто не исполнял, и шаг успеха, отвергнувший первый настоящий битстрим в истории задачи. Теперь задача выдаёт битстрим xc7a100t в 3 822 704 байта через полностью открытый поток.
CI-задача, собирающая битстрим для FPGA, была красной одиннадцать дней. Починка оказалась не починкой, а починками: тринадцать разных дефектов, сложенных так, что каждый можно было обнаружить, только вылечив предыдущий. Хвост задачи — синтез, трассировка, генерация фреймов, сборка битстрима — не исполнялся никогда. Ни разу за всю историю задачи.
Это стоит записать не потому, что тринадцать — большое число, а потому, что каждый слой был невидим снаружи, пока падал слой над ним. Конвейер, умирающий на шаге 3, не сообщает о шагах с 4 по 14 ничего — и «ничего» здесь буквально: ни строчки лога, ни ошибки типов, ни предупреждения линтера. Код, который никогда не исполнялся, — это не работающий код и не падающий код. Это пустое место.
| слой | что было сломано | почему слой ниже был невидим |
|---|---|---|
| 1–2 | цикл yosys-линта не мог пройти в принципе: выживал 1 модуль из 32; не хватало флагов -sv и -DSIMULATION | всё после линта пропускалось |
| 3–6 | четыре бага кодогенератора: ключевые слова Verilog как идентификаторы, экранирование до флэттенинга полей вместо после, неэкранированные вызовы и return-lvalue | сгенерированные файлы не доходили до синтеза |
| 7 | workflow клонировал YosysHQ/nextpnr — в котором архитектуры xilinx нет вовсе; она живёт в форке openXC7/nextpnr-xilinx | cmake умирал раньше всего остального |
| 8 | база данных кристалла была мегабайтом /dev/zero, и трассировщику не на что было опираться | фальшивый артефакт пропускал следующие шаги в ловушку |
| 9 | pip3 install sby — SymbiYosys нет на PyPI и никогда не было; формальная задача падала на установке в каждом прогоне с рождения | ничего после строки установки не исполнялось никогда |
| 10 | потолки времени: лимит в 45 минут убил здоровый прогон на 46 минут; отдельно apt на мёртвом зеркале молча сжигал целые потолки | убитая задача выглядит как упавшая |
| 11 | драйвер сборки жёстко зашивает build/fpga/prjxray, свой PYTHONPATH и путь к prjxray-db — а workflow складывал все три под ~/ | первый в истории прогон, дошедший до трассировки, умер на следующем шаге |
| 12 | requirements.txt в prjxray пинит editable-пути на сабмодули и инструменты, несовместимые с текущим Python; настоящая цепочка импортов — шесть пакетов | список зависимостей никто никогда не исполнял |
| 13 | шаг метрик требовал файл с буквальным именем bitstream.bit; драйвер называет выход по top-модулю — zerodsp_top.bit | первый настоящий битстрим в истории задачи провалил шаг, который должен был отчитаться об успехе |
Слой 8 заслуживает отдельного предложения. Фальшивый артефакт хуже отсутствующего: база из /dev/zero позволяла следующим шагам запускаться и падать не по той причине — так по-настоящему сломанное описание выводов пряталось за базой, через которую нельзя было оттрассировать ничего. Когда шаг ещё не умеет дать следующему настоящий вход, честный ход — упасть громко, а не изготовить заглушку.
Первые восемь слоёв снимались дорогим способом — по одному кругу CI на каждый, по сорок пять — девяносто минут за урок. Последние три сняты одним пушем, потому что каждый проверялся локально до коммита:
=== FASM → Frames ===
Frames: build/fpga/synth/design.frames
=== Frames → Bitstream ===
Bitstream: build/fpga/zerodsp_top.bit (3822704 bytes)
=== FPGA E2E build finished ===
3 822 704 байта — это точный размер конфигурации xc7a100t. Поток, который его произвёл, полностью открыт: yosys для синтеза, nextpnr-xilinx для трассировки по базе, сгенерированной bbaexport из дерева openXC7, fasm2frames из prjxray для сборки фреймов, xc7frames2bit для контейнера. Vivado нет нигде на этом пути.
Задача, которая всегда падала рано, содержит хвост, который никогда не исполнялся, — и этот хвост надо считать ненаписанным кодом: вычитывать, исполнять локально где возможно и никогда не доверять ему на основании правдоподобного вида. Следствие срезает цену: починив слой N, потрать лишние десять минут и проверь слой N+1 руками. Каждый из трёх последних слоёв был виден заранее любому, кто посмотрел бы: один grep, один --help, один листинг каталога в публичном репозитории.
Каждая цифра выше измерена, и рядом с ней названы её пределы.