T27.AI

Блог

Половина сборки — это генерация битстрима, а самым медленным оказался тривиальный дизайн

2026-08-15 · 7 мин чтения

Пятнадцать сборок openXC7 с объявленными границами: у малых дизайнов на превращение FASM в битстрим уходит больше времени, чем на трассировку, blinky проиграл умножителю GF, а разброс на побайтово одинаковой работе — 25%.

FPGAopenXC7BenchmarkingMeasurement

У всех есть представление о том, сколько занимает открытый тулчейн для Xilinx. Опубликованного числа с приложенным методом я не нашёл — а числа в нашем собственном репозитории оказались оценками, а не измерениями: три разных дизайна с одинаковым временем, по чему и видно, что секундомер никто не включал. Поэтому мы включили.

Что измеряется, а что нет

Границы — это и есть суть, потому что именно на них такие сравнения обычно жульничают.

ИзмеряетсяНе измеряется
синтез yosysdocker pull
размещение и трассировка nextpnr-xilinxgit checkout
fasm2frames + xc7frames2bitгенерация chipdb

chipdb собирается один раз отдельной задачей и раздаётся остальным артефактом — он одинаков во всех прогонах и лежит вне окна замера. Это свойство кристалла, а не дизайна, и у Vivado такого шага нет вообще; включать его значило бы соврать в чью-то пользу.

Пять прогонов на дизайн, сид зафиксирован на 1. Машина: общий раннер GitHub ubuntu-latest, 4 ядра. Кристалл: xc7a200tfbg484-2.

Числа

дизайнмедианаminmaxсинтезP&Rбитстрим
gf12_mul46.1с37.6с48.2с9.8с6.7с29.4с
blinky60.7с54.6с69.6с3.5с28.9с28.2с
gf128_mul533.2с521.5с662.6с163.1с253.7с122.3с

Тривиальный дизайн оказался не быстрым

Мигающий светодиод — 60.7 с. Умножитель GF(2^12) — 46.1 с. Умножитель крупнее по любой мерке и закончил на четверть быстрее.

Причина не в размере. blinky размещается плейсером отжига под ограничение 100 МГц, умножитель — аналитическим плейсером под 5 МГц. Трассировка blinky занимает 28.9 с против 6.7 с у умножителя — вчетверо, и в сторону, противоположную размерам. На этом масштабе время сборки решают плейсер и целевая частота, а нетлист почти не участвует.

Это стоит знать, прежде чем цитировать чей-либо результат на blinky, включая наш.

Половина времени уходит туда, о чём не говорят

Генерация битстрима — fasm2frames и следом xc7frames2bit — заняла 28.2 с у blinky и 29.4 с у умножителя. Это 46% и 64% полного времени сборки, и величина почти постоянна: дизайны различаются во всём остальном, а их битстрим-стадия расходится на секунду.

Она ведёт себя как пол. Для чего угодно малого больше времени уходит на превращение FASM во фреймы и фреймов в .bit, чем на размещение и трассировку. Усилия по оптимизации в этом тулчейне идут преимущественно в плейсер и роутер, где и находятся интересные алгоритмы; на малых дизайнах секунды лежат не там.

Пол перестаёт доминировать, когда дизайн достаточно велик. У gf128_mul битстрим — 122.3 с из 533.2 с, то есть 23%, тогда как трассировка занимает 253.7 с, а синтез 163.1 с. Эти две растут вместе с нетлистом, битстрим-стадия — гораздо медленнее.

Одно время сборки — это не измерение

Каждый прогон одного дизайна выполнял побайтово одинаковую работу: те же исходники, те же ограничения, тот же сид, тот же образ контейнера. Разброс всё равно такой:

дизайнmin → maxразброс к медиане
blinky54.6с → 69.6с25%
gf12_mul37.6с → 48.2с23%
gf128_mul521.5с → 662.6с26%

Около четверти, устойчиво, на трёх дизайнах с одиннадцатикратной разницей во времени. Это машина — общие раннеры с шумными соседями, — а не тулчейн, потому что тулчейну каждый раз давали один и тот же вход.

Практическое следствие: время из одного прогона может отстоять от медианы на четверть, и внутри этого прогона ничто об этом не скажет. Кто публикует одно число для любого из тулчейнов, публикует выборку из распределения, в которое не заглянул. Включая то число, которое опубликовали бы мы, запустив это один раз.

Две вещи, которые я сделал неправильно по дороге

Первая попытка потеряла все пять задач одного дизайна на синтезе: его обёртка инстанцирует ядро декодера из git-сабмодуля, а задача делала checkout без сабмодулей. Моя ошибка, локальная для одного дизайна — два других свои замеры выдали.

Исправление вышло хуже поломки. Добавленные сабмодули уронили сам шаг checkout, и второй прогон потерял все пятнадцать задач, включая два дизайна, которым сабмодуль был не нужен и которые часом раньше прошли чисто. Этот сабмодуль из CI недоступен вовсе. Декодеры выбыли из набора, их место занял самодостаточный умножитель GF(2^128) — поэтому диапазон размеров в таблице такой широкий.

Третья ошибка до прогона не дошла. В заголовке воркфлоу было написано, что пять повторов нужны из-за чувствительности nextpnr к сиду. Сид зафиксирован, значит повторы измеряют не это, а машину. Останься эта формулировка, пост открывался бы утверждением о плейсере, которого данные не подтверждают.

Что сделало бы это сравнением

Колонка Vivado, полученная на тех же трёх дизайнах с теми же границами, на машине, описанной так же точно. Мы её произвести не можем. Дизайны — обычные Verilog и XDC, воркфлоу открыт, так что вторую половину может добавить любой, у кого Vivado есть. А если половины разойдутся так, что ни одна сторона не воспроизведёт другую, — это и будет самым интересным исходом, а не провалом.

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

Пруфы

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