Недавно мне понадобилось заставить FPGA работать с обычным PCI. Не PCI Express, а тем самым старым параллельным PCI, платы с которым когда-то стояли почти в каждом компьютере, а сейчас он в основном продолжает жить в промышленной аппаратуре. Задача сама по себе не новая, и несколько лет назад я уже возился с этим интерфейсом. Тогда значительная часть времени уходила даже не на отладку платы, а на то, чтобы разобраться в стандарте, правильно понять процедуру конфигурации и руками написать всю необходимую логику на Verilog.
В этот раз рядом был ChatGPT. Я описывал ему очередной небольшой кусок логики, показывал существующий код, просил переделать конечный автомат или добавить нужное поведение и довольно быстро получал нормальную заготовку на Verilog. То, что раньше могло занять несколько часов, иногда действительно сокращалось до нескольких минут. После первой такой удачной итерации легко представить приятную картину будущего: ещё немного, и половину работы FPGA-разработчика можно будет просто отдавать нейросети.
На практике произошло другое. Освободившееся время я тут же потратил на следующую версию прошивки, потом на ещё одну, после чего пришлось несколько раз перепрошить FPGA, перезагрузить компьютер, посмотреть, как плата определяется BIOS, разобраться с BAR-регистрами и полезть к железу уже с измерительными приборами. За тот же промежуток времени я просто успел проверить гораздо больше вариантов. Именно тогда я поймал себя на довольно странной мысли: нейросети действительно сильно ускоряют мою работу, но её количество от этого почему-то совсем не уменьшается.
Для программиста, который большую часть дня работает внутри редактора кода, нынешний скачок AI выглядит как потенциальная угроза профессии. У инженера, который пишет код для реального устройства, всё складывается немного иначе. Код теперь получается быстрее, а значит, быстрее наступает момент, когда его надо загрузить в настоящую FPGA и выяснить, что именно на этот раз пошло не так.

Старый добрый PCI
Первая спецификация PCI появилась в 1992 году, а затем этот интерфейс надолго стал стандартным способом подключения плат расширения в обычных компьютерах. Позже его вытеснил PCI Express, который сохранил часть программной модели PCI, но на физическом уровне устроен уже совсем иначе. Поэтому сегодня разработка устройства именно под классический PCI выглядит довольно своеобразно: технология ещё прекрасно встречается в старой промышленной аппаратуре, но большая часть современной документации, обсуждений и готовых примеров уже давно посвящена PCI Express.
Когда начинаешь разбираться с каким-нибудь популярным современным интерфейсом, обычно довольно быстро находятся статьи, открытые проекты, репозитории на GitHub, ответы на Stack Overflow и обсуждения конкретных проблем. С PCI всё скромнее. Основным источником остаётся сама спецификация, плюс несколько старых сайтов и примеров HDL-кода, которые ещё сохранились в интернете. В своё время мне, например, я нашел fpga4fun, где можно было посмотреть хотя бы небольшие законченные примеры и понять общую логику работы устройства.
При этом сам PCI простым интерфейсом не назовёшь. Устройство имеет Configuration Space, Vendor ID и Device ID, BAR-регистры, участвует в конфигурационных транзакциях, должно правильно сообщить системе, какие ресурсы ему нужны, после чего BIOS или операционная система выделяют ему адресное пространство. Всё это замечательно работает, пока пользуешься готовой PCI-платой. Но когда пытаешься реализовать такое устройство самостоятельно внутри FPGA, выясняется, что теперь уже твоя логика должна вести себя именно так, как BIOS ожидает от настоящего PCI-устройства.
С этой частью я сталкивался ещё до нынешнего бума ChatGPT, и один из моментов, на котором я тогда особенно споткнулся, был связан с определением размера BAR. Механизм у PCI довольно красивый, но с первого раза совсем не очевидный: BIOS или другое power-up software записывает в Base Address Register все единицы, а затем читает его обратно. В спецификации это сформулировано так: “Power-up software can determine how much address space the device required by writing a value of all 1’s to the register”. После такой записи устройство возвращает нули в тех адресных разрядах, которые ему не нужны, и по получившейся маске система понимает, какой объём адресного пространства надо выделить. Когда читаешь это впервые сплошным текстом, приходится немного покрутить алгоритм в голове; в виде блок-схемы вся процедура укладывается буквально в несколько прямоугольников. И таких мест в старой спецификации PCI хватает.

И вот спустя несколько лет я вернулся примерно к той же задаче, только теперь рядом уже были современные нейросети. Оказалось, что с ними работать заметно проще, но появилась другая проблема: Verilog они знают лучше, чем сам PCI.
С Verilog всё неожиданно хорошо
Если попросить современную модель написать на Verilog счётчик, регистр, небольшой конечный автомат, буфер или какую-нибудь несложную управляющую логику, результат обычно получается вполне рабочим или по крайней мере достаточно близким к рабочему, чтобы его было проще поправить, чем писать заново. Для HDL уже существуют отдельные бенчмарки вроде VerilogEval и RTLLM, так что генерация RTL давно перестала быть просто забавным побочным применением языковых моделей.
Здесь, кстати, есть интересное отличие Verilog от языков общего назначения. Сам язык сравнительно небольшой, а типовые конструкции постоянно повторяются. Если модель понимает, что именно должен делать автомат, регистр или счётчик, написать соответствующий RTL для неё обычно не самая сложная часть задачи. Основные проблемы начинаются там, где заканчиваются знания о синтаксисе и требуется хорошо понимать конкретное железо, протокол или особенности стандарта.
С PCI это проявилось довольно быстро. Можно подробно описать нужный конечный автомат и получить нормальную заготовку. Можно показать кусок существующего кода и попросить добавить новый режим. Можно найти очевидную ошибку в работе с регистрами. Но если просто попросить написать полноценную PCI target-логику, нейросеть начинает зависеть от того же самого ограниченного набора информации, который доступен человеку. А если информации мало или она разбросана по старым документам, уверенный вид ответа ещё ничего не говорит о его корректности.
Поэтому на практике самым полезным оказался не режим «сделай за меня устройство», а работа маленькими кусками. Сначала я сам разбирался, что в конкретный момент должно происходить на шине, затем описывал эту логику модели и получал реализацию на Verilog. Потом проверял её уже на реальном устройстве, возвращался с результатом и двигался дальше. При этом делился с нейросетью своими успешными наработками. В таком режиме нейросеть действительно очень сильно ускоряет работу.
Несколько лет назад я пытался избавиться от Verilog совсем по-другому
Забавно, что в 2019 году я уже писал на Хабре о похожей проблеме, только тогда решение виделось совсем иначе. Речь была про MyHDL — попытку описывать цифровую логику на Python, а потом конвертировать её в Verilog или VHDL. Идея мне нравилась прежде всего тем, что снижала порог входа для обычного программиста: не обязательно сразу привыкать к HDL, можно начать с более знакомого языка.
Вокруг FPGA вообще много лет появлялись разные способы сделать разработку более похожей на обычное программирование. Кто-то использовал Python, кто-то C или C++, появлялись различные варианты высокоуровневого синтеза. Получалась дополнительная прослойка между инженером и HDL, которая должна была избавить его от необходимости вручную писать сравнительно низкоуровневое описание аппаратуры.
Сейчас я всё чаще думаю, что для многих небольших задач эта прослойка уже просто не нужна. Если мне требуется модуль на Verilog, я могу обычным человеческим языком описать его поведение и получить готовую реализацию. Не надо сначала переходить на Python только затем, чтобы из Python сгенерировать HDL. Получается, что нейросеть взяла на себя именно тот переход, который раньше пытались закрыть дополнительными языками и фреймворками.
Конечно, это не делает знание Verilog бесполезным. Код всё равно надо читать, понимать, проверять и иногда довольно серьёзно править. Но сам процесс набивания типовых конструкций руками уже явно перестал быть главным ограничением скорости работы.
А потом начинается железо
На этом месте история разработчика обычного приложения и инженера по FPGA начинает заметно расходиться. Допустим, код написан, проект синтезируется и никаких ошибок компилятор не показывает. Для обычной программы после этого можно запустить тесты и довольно далеко продвинуться вообще без контакта с физическим миром. С платой всё несколько иначе.
Надо подключить программатор, прошить FPGA, установить плату в компьютер и посмотреть, увидит ли её BIOS. Если не увидел (и вообще, если компьютер загрузился, а не стал издавать писк — это успех) — выключить компьютер, изменить логику, снова синтезировать проект, снова прошить FPGA и повторить всё сначала. Потом устройство наконец определяется, но BAR оказывается не таким, как ожидалось. После очередной правки конфигурация начинает проходить нормально, зато возникают вопросы уже к обмену данными. Тогда в дело идут осциллограф, логический анализатор или другие средства отладки, и разработка постепенно перемещается из редактора кода на рабочий стол с платой и проводами.
Именно здесь особенно хорошо ощущается предел нынешних AI-инструментов. Они могут помочь разобраться с кодом, объяснить кусок стандарта, предложить вариант конечного автомата или найти подозрительное место в RTL, но им всё ещё нужен тот самый кожаный мешок, который возьмёт программатор, подключит его к плате, переставит щуп осциллографа и заметит, что на конкретной линии почему-то совсем не тот уровень сигнала.
Часть такой работы, конечно, автоматизируется. Можно построить хороший испытательный стенд, написать автоматические тесты, использовать JTAG, встроенные логические анализаторы и HIL. Но при разработке нового устройства довольно быстро возникает ситуация, которую заранее никто не предусмотрел. Что-то не включилось, один из сигналов оказался инверсным, на плате обнаружилась особенность разводки, старый компьютер ведёт себя немного иначе, чем новый, или проблема вообще оказалась в контакте разъёма. Тут уже требуется не только код, но и физическое присутствие рядом с устройством.
Почему работы от ускорения стало больше
Самое интересное я заметил уже в процессе. Когда написание очередного варианта прошивки занимало много времени, некоторые идеи я просто не проверял. Если для небольшой гипотезы надо потратить два дня на переписывание логики, очень быстро начинаешь выбирать только те варианты, которые кажутся наиболее перспективными.
Теперь порог проверки стал намного ниже. Можно сформулировать идею, быстро переделать нужный кусок RTL, синтезировать проект и посмотреть, что получилось на реальном железе. Не сработало — через какое-то время готов второй вариант. Потом третий. В результате AI действительно экономит время на написании кода, но это время почти сразу уходит на дополнительные эксперименты, которые раньше просто не дошли бы до практической проверки.
Поэтому лично у меня нейросети не создали ощущения, что инженерной работы стало меньше. Скорее они заметно сократили наиболее рутинную часть и позволили быстрее переходить к следующей инженерной задаче. За тот же рабочий день можно проверить больше гипотез, найти больше проблем и в итоге сделать более сложное устройство. Для работодателя это, наверное, отличная новость. Для инженера, который рассчитывал после внедрения AI освободить половину рабочего дня, не очень.
Если код больше не главное узкое место, что будет следующим?
На этом месте мне хочется немного отойти от PCI. У меня всё сильнее складывается ощущение, что человечество довольно быстро снимает ограничение, связанное именно с ручным написанием программного кода. Не думаю, что имеет смысл заявлять какой-то конкретный процент вроде «80% программирования уже решено»: проекты слишком разные. Но огромный пласт рутинных задач — написать функцию, переделать класс, добавить тесты, найти типовую ошибку, разобраться с API, сделать простой драйвер или собрать прототип — сегодня выполняется заметно быстрее, чем ещё несколько лет назад.
И тогда естественно возникает вопрос, где окажется следующее узкое место. Мне кажется, что оно всё больше смещается в физический мир, и особенно хорошо это видно в робототехнике.
Здесь у меня возникает аналогия с IBM System/360. Кстати, пока готовил статью, обнаружил, что сам долго держал в голове неправильную дату: System/360 появился не в 1980-х, а был представлен IBM ещё 7 апреля 1964 года. Главное его значение для этой аналогии в том, что IBM предложила не просто отдельный компьютер, а целое семейство совместимых машин с общей архитектурой. Пользователь мог перейти на более производительную систему и не начинать всё программное обеспечение заново.
С вычислительной техникой идея универсальной платформы в итоге победила настолько, что мы почти перестали об этом задумываться. Компьютеры могут сильно отличаться по мощности и назначению, но одна и та же общая вычислительная модель подходит для офиса, игр, банкомата, промышленного оборудования, телефона и огромного количества других задач.
В робототехнике такой универсальности пока заметно меньше. Робот, который ездит по складу, промышленный манипулятор, квадрокоптер и человекоподобная машина могут использовать похожие алгоритмы и даже одинаковые вычислительные модули, но с точки зрения механики, приводов, сенсоров и безопасности это всё ещё очень разные устройства. Даже два гуманоидных робота разных производителей могут требовать совершенно разных инженерных решений.
Поэтому мне кажется интересной идея некоего робототехнического аналога System/360 — не обязательно одного универсального гуманоида на все случаи жизни, а достаточно общей аппаратной и программной платформы, на которую можно переносить навыки и готовые решения без полной переработки всего устройства. Сейчас мы умеем довольно хорошо переносить программный код между компьютерами. Следующий большой шаг, возможно, будет связан с тем, чтобы научиться так же переносить поведение между физическими машинами.
И вот тогда вопрос о замене инженеров станет действительно интереснее. Сейчас я могу написать нейросети: «Сделай конечный автомат для такого-то режима», получить Verilog и через некоторое время проверить его на FPGA. Но я пока не могу написать ей: «Возьми эту плату, разберись, почему она не определяется по PCI, проверь питание, посмотри сигналы на шине, исправь RTL, перепрошей FPGA и повторяй, пока всё не заработает».
Для выполнения такой команды одного хорошего генератора кода уже недостаточно. Нужна машина, которая способна самостоятельно работать с физическим миром примерно так же свободно, как современные AI-агенты работают с файлами и программами: взять инструмент, найти нужный разъём, подключиться, измерить сигнал, понять результат и попробовать следующий вариант.
Вот тогда у инженера с осциллографом действительно появится серьёзный конкурент.
А пока получилось довольно забавно. ChatGPT заметно ускорил мне написание Verilog, но вместо свободного времени я получил возможность быстрее добираться до следующей проблемы. И чем лучше становятся такие инструменты, тем больше экспериментов успеваешь провести и тем больше реального железа хочется попробовать сделать.

