Блог
Гейт, написанный чтобы ошибки элаборации не возвращались, сообщил про 186 штук; 25 из них были собственной итоговой строкой компилятора о том, сколько ошибок он нашёл. Число уже успело уехать в коммиты и на страницу статуса. Доказательство, которое не могло получиться иначе, живой дефект эмиттера, прятавшийся внутри счёта, почему от починки число стало хуже, и одна починка, которую построили, измерили и выбросили.
Гейт, написанный чтобы ошибки элаборации не возвращались, сообщил про 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. Строгая регрессия в обмен на две нелегальные строки. Ветка удалена, а вопрос заведён входом к тому решению, от которого он на самом деле зависит.
Из первого черновика того же разбора пришлось убрать и собственное утверждение: будто поле нулевой ширины портит офсеты всего, что за ним. Не портит. Раскладка остаётся самосогласованной, поле просто не имеет хранилища, а чтение и запись падают громко. Эта разница решает, какая починка правильная, — потому и стоило измерить на четырёхстрочном примере вместо того, чтобы утверждать.
Гейт — это прибор, и приборы ломаются так же, как то, что они измеряют. Если гейт сообщает число, один раз классифицируй его сырой вывод по форме сообщения: напечатай распределение, прочитай каждую строку и убедись, что каждая — то, что ты собирался считать. Считать строки не значит классифицировать. А когда вывод гейта становится фразой, которую говорят о проекте, гейт повышен до публикации — независимо от того, принимал ли кто-нибудь такое решение.
Каждая цифра выше измерена, и рядом с ней названы её пределы.