Постановка задачи

Для чего нам нужна система сборки?

Для автоматической сборки проектов, которая легко запускается и нивелирует возможные ошибки разработчика (очень обидно под конец текущей сборки вспомнить, что забыл обновить память процессора новой программой, а проект ПЛИС ведь долго собирается!).

На выходе хотелось бы получить образ конфигурирующей ПЛИС флешки (прошивку).

Система должна быть максимально гибкой, чтобы ее можно было использовать для разных проектов, и легко масштабируемой для ее последующего развития.

Каким образом можно собирать отдельные компоненты проекта?

Через TCL-скрипты (а TCL глубоко интегрирован в Quartus) или через запуск отдельных утилит в консольном режиме. Требования по гибкости и масштабируемости намекают на использование модульной архитектуры, а значит лучше всего разбросать функционал по отдельным TCL-скриптам.

Система сборки призвана не заменить обычный flow разработки в Quartus, но дополнить ее − она позволит удобно получать релизные прошивки.

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

Функциональные возможности системы сборки

Перечислим возможности системы:

  • генерация IP-ядер (как HDL, так и Qsys);

  • сборка программных проектов для Nios II;

  • сборка дизайна (синтез, имплементация) и получение битстрима;

  • проверка таймингов на Failing Paths;

  • генерация образа флешки.

Запуск этих операций проводится через старый добрый make, который в свою очередь запускает необходимые TCL-скрипты через интерпретатор Quartus.

Проверенные версии Quartus – 15.1 и 18.1.

Что с версиями поновее? Начиная с версии 19.1, Intel заменила утилиты для программной сборки Cygwin на WSL. При проверке Quartus 20.1 программный проект для Nios II ожидаемо не собрался, но остальной функционал работал.

 В данный момент система сборки поддерживает только Windows.

Настройка компьютера

Теперь поговорим о необходимой настройке компьютера для работы сборки.

Во-первых, необходимо добавить в переменную PATH пути до bin-папки ваших Quartus (их может быть несколько). Пример:

C:\altera\15.1\quartus\bin64

C:\intelFPGA\18.1\quartus\bin64

Во-вторых, нужно установить утилиту make. Мы пользуемся этой из набора MinGW-w64. Не забываем указать путь к make в PATH.

В-третьих, следует разрешить выполнение скриптов Powershell, так как в состав системы сборки входит скрипт, который анализирует по QPF-файлу используемую версию Quartus и сортирует PATH в рамках текущей сессии (глобально PATH не меняется).

Настройка проекта

Во время изучения возможности автоматической генерации IP-ядер мы столкнулись со следующей проблемой: Quartus не умеет самостоятельно их генерировать во время сборки дизайна, всегда необходимо самому заходить в MegaWizard для генерации обычных HDL-ядер и в Platform Designer (Qsys) для генерации Qsys-компонентов. К слову, Xilinx Vivado не имеет с этим проблем и спокойно собирает свои ядра во время сборки дизайна или отдельной TCL-командой.

Мы решили описывать все используемые ядра в отдельном файле и «скармливать» его системе сборки. Вся остальная настройка проекта будет происходить в makefile, но о нем чуть попозже.

Заполняется информация в файле в JSON-формате. Пример:

{
    "nios_system" : 
    {
        "type" : "qsys",
        "file" : "ip\\nios_system\\nios_system.qsys",
        "output_directory" : "ip\\nios_system"
    },
     "cyc_v_pll" :
    {
        "type" : "hdl",
        "file" : "ip\\cyc_v_pll\\cyc_v_pll.v"
    }
}

Здесь описаны два ядра − nios_system и cyc_v_pll, как раз достаточных для примера, так как типов ядер тоже два − Qsys и HDL.

Ключи-строчки nios_system и cyc_v_pll − просто названия ядер. Для каждого ядра необходимо определить тип ("type") и указать относительный путь до файла ("file"). Если ядро реализовано MegaWizard'ом на VHDL или Verilog, то указываем тип "hdl", если ядро является Qsys-компонентом − "qsys". Путь до файла относителен директории проекта (там, где лежит QPF-файл).

В случае описания Qsys-ядра необходимо также указать "output_directory" − директорию, где сохраняется результат генерации ядра. Она должна совпадать с той, на которую настроен Platform Designer при обычной генерации через GUI.

Теперь можно поговорить про настройку makefile для проекта. Вот предлагаемый пример.

Как можно заметить, в makefile есть блок переменных editable parameters, который предлагается настраивать для каждого проекта:

  • REPO_DIR − путь до репозитория относительно директории с makefile;

  • SCRIPTS_DIR − путь до скриптов, которые и выполняют основную работу.
    Скрипты не обязательно должны лежать в этом же репозитории, также их можно подключить как submodule;

  • REVISION − ревизия Quartus-проекта по умолчанию.
    При необходимости собирать другую ревизию, следует передать другое значение переменной во время вызова make и тем самым перебить ревизию по умолчанию − этим удобно пользоваться на CI;

  • IP_JSON_PATH − путь до файла, описывающего ядра, о котором мы недавно говорили;

  • WORKSPACE − путь до программного workspace;

  • SW_APP − программный проект по умолчанию;

  • SW_BSP − BSP для проекта SW_APP;

  • COF_PATH − путь до COF-файла, который нужен для преобразования битстрима в образ флешки.

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

make sw

соберет программный проект, выбранный по умолчанию, а также создаст необходимый образ памяти программы и mem_init файл.

make sw SW_APP='another_app' SW_BSP='another_bsp'

сделает то же самое, но для другого программного проекта. Для упрощения сборки нескольких программных проектов в makefile была добавлена цель sw_all, в которой можно собрать все необходимые проекты, что удобно, когда используется несколько микропроцессорных систем Nios II.

Разумеется, присутствует цель all, которая полностью соберет Quartus-проект для выбранной ревизии.

Перейдем к гвоздю программы, а именно к сборке проекта на удаленном сервере.

Continuous Integration

Конвейер сборки нашего тестового проекта выглядит следующим образом:

Уже по этой картинке, а также по YAML-файлу, описывающему CI, видно, что сборка происходит в четыре этапа:

  1. IP generate;

  2. Software build;

  3. Bitstream build;

  4. Flash build.

IP generate

Генерация IP-ядер согласно списку, который хранится в JSON-файле.

Артефакты задания − файлы ядер, которые передаются на следующие этапы и живут короткий промежуток времени.

Software build

Сборка программных проектов.

В данном конвейере собирается один проект, но ничего не мешает собирать их несколько под один Nios II или под разные Nios II.

Артефакты задания − ELF-файл и содержимое папки mem_init, которая хранит образ памяти и QIP-файл, нужный Quartus'у для нахождения этого образа.

Bitstream build

 На данном этапе сборки происходит несколько операций:

  1. Удаление из QSF-файла упоминаний файлов mem_init.qip, которые были в нем на момент коммита.

  2. Добавление выбранных пользователем mem_init.qip (считай, программных проектов).

    Данную операцию можно проводить несколько раз, для разных проектов, когда в дизайне используются несколько процессоров Nios II.

  3. Синтез дизайна.

  4. Имплементация дизайна.

  5. Генерация битстрима (SOF-файла).

  6. Проверка плохих таймингов в Timing Analyzer.
    Тайминги проверяются для обеих моделей (slow/fast) и для разных температур (min/max, в проекте это 0/85°C).

В тестовом проекте собираются битстримы для двух разных ревизий (для двух разных отладочных плат).

Артефакты задания − битстрим и файлы репортов.

Flash build

Генерация образа флешки из битстрима согласно COF-файлу.

Артефакт задания − образ флешки (JIC-файл).

Заключение

Репозиторий проекта со скриптами, CI и тестовым дизайном для сборки располагается здесь.

Надеемся, наш подход вам пригодится. Лично нам CI здорово упростил жизнь при создании релизных прошивок для множества ревизий проекта, а ведь еще можно добавить в него верификацию и тогда дизайн будет тестироваться каждый раз при пуше!

Комментарии и обсуждение приветствуются.

Raccoon Security – специальная команда экспертов НТЦ «Вулкан» в области практической информационной безопасности, криптографии, схемотехники, обратной разработки и создания низкоуровневого программного обеспечения.