T27.AI

Блог

Храповик посчитал итог за ошибку

2026-08-23 · 8 мин чтения

Гейт, написанный чтобы ошибки элаборации не возвращались, сообщил про 186 штук; 25 из них были собственной итоговой строкой компилятора о том, сколько ошибок он нашёл. Число уже успело уехать в коммиты и на страницу статуса. Доказательство, которое не могло получиться иначе, живой дефект эмиттера, прятавшийся внутри счёта, почему от починки число стало хуже, и одна починка, которую построили, измерили и выбросили.

CIMeasurementSelf-critiqueFPGACompilers

Гейт, написанный чтобы ошибки элаборации не возвращались, сообщил про 186 штук. Двадцать пять из них ошибками не были. Это строка, которую iverilog печатает в конце упавшего файла, чтобы сказать, сколько ошибок он нашёл.

Одна подстрока, двадцать пять фантомов

Счётчик ловил любую строку stderr, содержащую подстроку « error». Это очевидный способ считать ошибки компилятора, и здесь он неверен: компилятор закрывает каждый упавший файл итогом «N error(s) during elaboration.» Итог содержит эту подстроку. По фантому на каждый упавший модуль, каждый раз.

Записывать это стоит не ради бага — баг двухстрочный, — а потому что число уже уехало. Оно стояло в сообщениях коммитов, в комментарии к issue и на странице статуса в виде «573 → 186, минус 68%». Оба конца были завышены одним и тем же механизмом. Направление и доля уцелели; абсолютные значения — нет.

Доказательство, а не рассуждение

В диагноз «ошибка счёта» легко поверить и легко ошибиться, поэтому стоит настаивать на форме, которая не могла получиться иначе. После исключения итоговой строки каждый модуль падает ровно на единицу. Таких модулей двадцать пять. И 186 − 161 = 25 — это же число модулей, вообще не проходящих элаборацию. Три независимые величины, сходящиеся на одной и той же двадцатке с пятёркой, — это не рассказ о починке, это и есть починка.

$ tools/check_elab_ratchet.py
elaboration errors: 161 (baseline 186)
  BETTER  apb_bridge: 5 -> 4
  BETTER  assembler: 5 -> 4
  BETTER  axi4:       3 -> 2
  ... ещё 22, и каждый ровно на -1

# 25 модулей x 1 фантом = 25;  186 - 161 = 25;  упавших модулей = 25

В том же файле стояло и ложное утверждение

Докстрока говорила, что оставшиеся ошибки — это «два именованных дизайн-решения». Это было измерено — по одному классу ошибок, несвязанным идентификаторам, где утверждение точно: 56 чтений строковых полей и 12 чтений безразмерных массивов, больше ничего. А написано было так, будто описывает все. Оно описывало 68 из 161.

Остальные 93 не были классифицированы вообще. Напечатать распределение по форме сообщения вместо подсчёта строк — одна команда, и вот что вышло:

Дефект, прятавшийся в счёте

Параметр по имени «cross» — ключевое слово SystemVerilog — экранировался там, где объявлен, и печатался голым там, где используется, так что сгенерированный part-select читался как ключевое слово, а компилятор отвечал сухим «syntax error». Корень — одна переменная на две работы: одна и та же строка служит и ключом поиска типа в таблице, где обязана остаться сырой, и печатаемым текстом, где обязана быть экранированной. Ключ печатали в пяти местах.

Ровно эта форма чинилась в этой кодовой базе однажды — для локального массива с именем-ключевым словом, с пометкой, что пути выражений уже верны. Пути part-select никто не прочесал. Когда чинишь «экранировано здесь, но не там», полезный следующий шаг — грепнуть все остальные места, где это значение печатается, прежде чем закрывать.

И от починки число стало хуже

Снятие четырёх синтаксических ошибок подняло счёт модуля с четырёх до пяти, и храповик справедливо отказался пропускать изменение, пока его не объяснят. Синтаксическая ошибка обрывает файл. За теми четырьмя стояли пять настоящих ошибок элаборации, до которых разбор не доходил, — их и не считали никогда. Синтаксическая ошибка стоит дороже своего номинала, а храповик — это защита от тихого сползания, а не оценка качества.

Объяснение легло в файл baseline рядом с числом, а не только в пул-реквест. Следующий, кто сюда посмотрит, откроет файл.

И одна починка, которую построили, измерили и выбросили

Оставшиеся ошибки одного модуля вели к нашему же более раннему изменению: строковым полям дали нулевую ширину, чтобы одна строка не блокировала числовые поля структуры. Верно, когда числовые поля есть. Когда все поля строковые — а таких структур в корпусе четырнадцать, — структура пакуется в ноль бит, а part-select нулевой ширины нелегален.

Очевидный гвард — отвергать структуры нулевой ширины. Он убирает оба нелегальных part-select и каскадирует: у объемлющей структуры появляется недопустимый тип поля, она перестаёт лоуериться, и числовые чтения, работающие сегодня, превращаются в несвязанные имена. Измерено, а не предсказано: модуль ушёл с 16 ошибок на 33, корпус со 162 на 179. Строгая регрессия в обмен на две нелегальные строки. Ветка удалена, а вопрос заведён входом к тому решению, от которого он на самом деле зависит.

Из первого черновика того же разбора пришлось убрать и собственное утверждение: будто поле нулевой ширины портит офсеты всего, что за ним. Не портит. Раскладка остаётся самосогласованной, поле просто не имеет хранилища, а чтение и запись падают громко. Эта разница решает, какая починка правильная, — потому и стоило измерить на четырёхстрочном примере вместо того, чтобы утверждать.

К чему это обобщается

Гейт — это прибор, и приборы ломаются так же, как то, что они измеряют. Если гейт сообщает число, один раз классифицируй его сырой вывод по форме сообщения: напечатай распределение, прочитай каждую строку и убедись, что каждая — то, что ты собирался считать. Считать строки не значит классифицировать. А когда вывод гейта становится фразой, которую говорят о проекте, гейт повышен до публикации — независимо от того, принимал ли кто-нибудь такое решение.

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

Пруфы

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