T27.AI

Блог

Хвост, который никогда не исполнялся

2026-08-19 · 9 мин чтения

Собирающая битстрим CI-задача была красной одиннадцать дней. Под этим — тринадцать слоёв дефектов, каждый невидим, пока не вылечен верхний: фальшивая база кристалла, список зависимостей, который никто не исполнял, и шаг успеха, отвергнувший первый настоящий битстрим в истории задачи. Теперь задача выдаёт битстрим xc7a100t в 3 822 704 байта через полностью открытый поток.

CIFPGAopenXC7DebuggingSelf-critique

CI-задача, собирающая битстрим для FPGA, была красной одиннадцать дней. Починка оказалась не починкой, а починками: тринадцать разных дефектов, сложенных так, что каждый можно было обнаружить, только вылечив предыдущий. Хвост задачи — синтез, трассировка, генерация фреймов, сборка битстрима — не исполнялся никогда. Ни разу за всю историю задачи.

Это стоит записать не потому, что тринадцать — большое число, а потому, что каждый слой был невидим снаружи, пока падал слой над ним. Конвейер, умирающий на шаге 3, не сообщает о шагах с 4 по 14 ничего — и «ничего» здесь буквально: ни строчки лога, ни ошибки типов, ни предупреждения линтера. Код, который никогда не исполнялся, — это не работающий код и не падающий код. Это пустое место.

Луковица, сжато

слойчто было сломанопочему слой ниже был невидим
1–2цикл yosys-линта не мог пройти в принципе: выживал 1 модуль из 32; не хватало флагов -sv и -DSIMULATIONвсё после линта пропускалось
3–6четыре бага кодогенератора: ключевые слова Verilog как идентификаторы, экранирование до флэттенинга полей вместо после, неэкранированные вызовы и return-lvalueсгенерированные файлы не доходили до синтеза
7workflow клонировал YosysHQ/nextpnr — в котором архитектуры xilinx нет вовсе; она живёт в форке openXC7/nextpnr-xilinxcmake умирал раньше всего остального
8база данных кристалла была мегабайтом /dev/zero, и трассировщику не на что было опиратьсяфальшивый артефакт пропускал следующие шаги в ловушку
9pip3 install sby — SymbiYosys нет на PyPI и никогда не было; формальная задача падала на установке в каждом прогоне с рожденияничего после строки установки не исполнялось никогда
10потолки времени: лимит в 45 минут убил здоровый прогон на 46 минут; отдельно apt на мёртвом зеркале молча сжигал целые потолкиубитая задача выглядит как упавшая
11драйвер сборки жёстко зашивает build/fpga/prjxray, свой PYTHONPATH и путь к prjxray-db — а workflow складывал все три под ~/первый в истории прогон, дошедший до трассировки, умер на следующем шаге
12requirements.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, один листинг каталога в публичном репозитории.

Чего это не решает

Пруфы

Каждая цифра выше измерена, и рядом с ней названы её пределы.