Тестовый проект🔗
Цель🔗
Настоящий документ преследует цель дать подробное описание использования основных возможностей системы сборки FPGA проектов. Сюда относится:
- способность генерировать файлы параметров на целевых языках (SystemVerilog, Tcl) из исходных конфигурационных файлов формата YAML;
- генерировать и синтезировать IP ядра;
- создавать блочные дизайны (БД);
- компилировать HLS описания;
- создавать и компилировать библиотеки симуляционных моделей от IP ядер, блочных дизайнов, HLS IP;
- запускать симулятор в консольном и графическом режимах;
- создавать проект САПР Vivado, подключая всё необходимое: исходные файлы, IP ядра, блочных дизайны и т.п.;
- синтезировать проект;
- осуществлять размещение и трассировку (P&R).
Код проекта, описываемого настоящим документом, доступен по ссылке.
Краткое описание проекта🔗
FPGA проект описывает сумматор, принимающий два операнда и возвращающий сумму их значений. Проект содержит 3 сборочных варианта, каждый из которых демонстрирует собственный способ реализации указанной функции. Проект не претендует на логическую правильность и ни в коем случае не является эталоном проектирования, главная его цель — продемонстрировать возможности системы сборки и показать типовые приёмы её использования.
Сборочных вариантов, как было сказано выше, 3, они названы по именам целевых отладочных плат Xilinx:
- 7a35t. Реализует традиционный HDL способ описания сумматора.
- 7a50t. Функция суммирования реализована в виде HLS описания.
- ac701. Модуль суммирования выполнен на основе блочного дизайна.
Проект является параметризованным, каждый сборочный вариант (СВ) имеет собственные значения параметров. Во избежание дублирования общие части конфигурации размещены в одном, общем для всех сборочных вариантов, месте. Все СВ собираются из единого дерева исходных файлов.
Структура дерева исходных файлов:
└── src # директория с исходными файлами
├── cfg # директория с описаниями сборочных вариантов (конфигураций)
│ ├── 7a35t # директория сборочного варианта 7a35t
│ │ ├── 7a35t.scons # сборочный скрипт данного СВ
│ │ ├── env # конфигурационные файлы СВ
│ │ ├── script # вспомогательные скрипты СВ
│ │ ├── sim # командные и конфигурационные файлы, используемые при работе с симулятором
│ │ └── xdc # файлы констрейнов
│ ├── 7a50t # директория сборочного варианта 7a35t
│ │ ├── 7a50t.scons # сборочный скрипт данного СВ
│ │ ├── env # см. выше
│ │ ├── hls # конфигурационные файлы HLS описаний
│ │ ├── script # см. выше
│ │ ├── sim # см. выше
│ │ └── xdc # см. выше
│ ├── ac701 # директория сборочного варианта ac701
│ │ ├── ac701.scons # сборочный скрипт данного СВ
│ │ ├── bd # описание блочных дизайнов
│ │ ├── env # см. выше
│ │ ├── script # см. выше
│ │ ├── sim # см. выше
│ │ └── xdc # см. выше
│ └── common # директория общих для всех СВ определений
│ ├── build_base.py # базовый скрипт сценариев сборки
│ ├── env # общие конфигурационные файлы
│ ├── ip # конфигурационные файлы общих IP ядер
│ └── script # общие для всех СВ скрипты.
├── hls # исходные файлы HLS описаний
│ └── adder #
│ ├── src # синтезируемое HLS описание
│ └── tb # файлы симуляции на уровне C/C++ (цель csim)
├── sim # исходные файлы симулятора
│ └── top_tb.sv #
└── syn # исходные файлы для синтеза
├── adder_bd.sv # исходный файл-"обёртка" для сумматора в виде БД
├── adder_hls.sv # исходный файл-"обёртка" для сумматора, выполненного на основе HLS
├── adder_if.sv # исходный файл с описанием интерфейсов
└── top.sv # исходный файл верхнего уровня
Использование🔗
Запуск на сборку осуществляется с помощью команды scons [options] variant=<variant-name> [target]
из корневой директории проекта (там, где расположен файл SConstruct
) или из любого места дерева проекта при указанной опции -D
.
Примеры команд🔗
Показать справку по целям🔗
Создать проект Vivado🔗
Будет создан проект Vivado, предварительно сгенерированы IP ядра с последующим добавлением в проект.
Синтезировать проект для варианта 7A50T🔗
Открыть проект для варианта 7A35T🔗
Здесь попутно будут синтезированы IP ядра, т.к. после открытия проекта в Vivado можно сразу приступать к синтезу непосредственно проекта. Для выбора варианта тут используется аргумент bv
вместо variant
, они являются синонимами.
Компилировать рабочую библиотеку симулятора (цель по умолчанию)🔗
Цель по умолчанию wlib
, её указание можно опустить. По умолчанию выбрана эта цель, т.к. её запуск является самым частым – это быстрый и удобный способ проверить исходный HDL код на синтаксическую правильность, а так же компиляция рабочей библиотеки требуется при отладке в симуляторе.
Реализация🔗
Методика🔗
Несмотря на значительные различия в реализации, все три сборочных варианта имеют много общего. Общего как в конфигурационных параметрах, IP ядрах, так и (что очень существенно) в сценарии сборки. Собственно, сценарий сборки для всех вариантов почти один и тот же, различия касаются в основном необходимости в том или ином СВ выполнить дополнительные действия — подключить БД или HLS описание.
Скрипт сценария сборки🔗
Скрипт сценария сборки — это описание на языке программирования (ЯП) Python, передаваемое утилите scons. Традиционный способ описания — в виде обычных выражений ЯП Python представлен в этом примере: в нём каждый сборочный вариант имеет свой собственный отдельный скрипт сборочного сценария (*.scons
файл), описывающий полностью самостоятельно весь сценарий сборки. Друг от друга эти скрипты отличаются достаточно незначительно, т.е. имеют обширную общую часть. Это делает процесс модификации весьма обременительным — например, при внесении какого-либо изменения в скрипт сборочного сценария какого-нибудь СВ, если это изменение касается и других СВ, возникает необходимость вручную править соответствующие места и скриптах сборочных сценариев этих СВ.
В настоящем примере применён подход, свободный от вышеописанного серьёзного недостатка. Благодаря возможностям ЯП Python общая часть скриптов сборочных сценариев выделена в специальный объект — класс, этапы сборочного сценария оформлены в виде функций-членов этого класса, их запуск осуществляется из конструктора класса. Таким образом, для запуска сборочного сценария достаточно просто создать объект класса. Если сборочный сценарий какого-то СВ должен выполнять действия, отличные от заданных в базовом скрипте (классе), то тут существует ряд штатных для ЯП Python способов изменять функциональность — например, создать класс-наследник от базового и переопределить в нём нужную функцию. Механизм наследования позволяет эффективно вынести общую часть в базовый класс, а различия определять в классах-потомках.
Собственно, конструктор класса достаточно наглядно представляет схему сборочного сценария:
class BuildBase:
def __init__(self, env, **src_dict):
self.envx = env
src_keys = ['src_syn', 'src_sim', 'ip', 'bd', 'hls']
for k in src_dict:
if not k in src_keys:
print_error('E: invalid source key \'' + k + '\' specified for build class constructor')
print_error(' valid source keys: \'' + '\', \''.join(src_keys) + '\'')
Exit(-1)
self.src_dict = src_dict
self.setup_search_paths()
self.add_sources()
self.setup_constr_env()
self.add_hls_script_targets()
self.add_hls_targets()
self.add_ip_targets()
self.add_bd_targets()
self.add_hdl_params_targets()
self.add_tcl_params_targets()
self.add_main_targes()
self.add_phony_targes()
self.setup_explicit_dependensies()
self.setup_default_targets()
self.define_target_aliases()
self.setup_target_help()
self.setup_extensions()
...
Конструктор класса принимает один обязательный аргумент — это объект сборочного окружения (СО) env
, и некоторое количество опциональных именованных аргументов, которые позволяют задавать:
src_syn
: списки исходных файлов для синтеза;src_sim
: списки исходных файлов для симуляции;ip
: списки конфигурационных файлов для IP ядер;bd
: списки конфигурационных файлов для блочных дизайнов;hls
: списки конфигурационных файлов HLS описаний.
Все эти списки файлов сохраняются во внутренней переменной класса и в дальнейшем используются для построения сценария сборки. Возможность задавать списки исходных и конфигурационных файлов как аргументы конструктора класса позволяет абстрагировать исполняемый код от данных, которыми являются эти файлы списков. В каждом СВ скрипт сборочного сценария может указывать индивидуальный набор списков исходных и конфигурационных файлов.
Далее в конструкторе идёт вызов функций-членов, которые осуществляют все основные этапы работы скрипта сборочного сценария. Этапы работы сценария сборки описаны в таблице:
Stage Name | Function | Description |
---|---|---|
Настройка путей поиска |
setup_search_paths() |
Формирование списка путей поиска конфигурационных файлов. Подробнее о правилах поиска конфигурационных файлов. |
Создание списков исходных файлов |
add_sources() |
Формирование внутренних объектов-списков, содержимое которых является зависимостями для различных целей скрипта сценария сборки. |
Настройка сборочно- го окружения |
setup_constr_env() |
Установка значений переменных СО. |
Описание целей | add_hls_script_targets() add_hls_targets() add_ip_targets() add_bd_targets() add_hdl_params_targets() add_tcl_params_targets() add_main_targes() add_phony_targes() |
Описание цепочек целей по зависимостям |
Указание явных зависимостей |
setup_explicit_dependensies() |
Настройка явных зависимостей для некоторых целей |
Определение целей по умолчанию |
setup_default_targets() |
Цели по умолчанию — это те, которые собираются когда цели при запуске не указаны явно. |
Описание псевдони- мов целей |
define_target_aliases() |
Псевдонимы целей — это, как правило, более короткие и удобные при запуске из командной строки имена целей. |
Описание справки по запуску |
setup_target_help() |
Выводит справку по целям командной строки |
Добавление расширений |
setup_extensions() |
Добавление вспомогательных функций таких как: формирование отчёта о предупреждениях, таймин- гах, утилизации, возможность запус- ка сторонней программы просмотра логов синтеза и P&R. |
Конфигурационные файлы🔗
Второй аспект, влияющий на результат сборки помимо скрипта сборочного сценария, — это используемый набор конфигурационных файлов. Именно они определяют списки файлов исходного кода, констрейнов, IP ядер и т.п., а так же параметры сборки. Как и в случае со скриптом сборочного сценария общая для всех СВ часть конфигурации вынесена в cfg/common
. Структура директорий такая же, как и в рабочих сборочных вариантах. Сценарий сборки написан так, что конфигурационный файл сначала ищется внутри дерева файлов самого СВ, и если файл не найден, поиск переходит в дерево файлов cfg/common
.
Сборочные варианты🔗
7a35t🔗
Данный СВ реализует самый простой способ сумматора — непосредственно средствами HDL. Он не требует никаких дополнительных модулей и не требует модификации базового сборочного скрипта. Всё, что необходимо для организации сборки — это создать объект класса, конструктор которого выполнит все необходимые действия:
#-------------------------------------------------------------------------------
#
# Environment
#
Import('envx')
bv = BuildBase(envx)
ac701🔗
Этот СВ использует для реализации сумматора блочный дизайн (методика создания блочных дизайнов из Tcl конфигурационных скриптов). В этом случае необходимо добавить в проект конфигурационный Tcl скрипт с описанием БД и подключить его к проекту:
#-------------------------------------------------------------------------------
#
# Environment
#
Import('envx')
bv = BuildBase(envx, bd='bd.yml')
7a50t🔗
Здесь реализация сумматора выполнена на основе HLS описания. Помимо самого HLS описания СВ поддерживает сборочную цель HLS csim
— симуляция HLS описания на уровне C/C++. Для этого с скрипт сборочного сценария добавлен код для подключения этой цели:
#-------------------------------------------------------------------------------
#
# Environment
#
Import('envx')
class Build7a50t(BuildBase):
def __init__(self, env, **src_list):
super().__init__(env, **src_list)
self.adder = SConscript(os.path.join('hls', 'adder', 'adder.scons'), exports = 'envx')
Depends(self.LaunchQuestaRun, self.adder)
bv = Build7a50t(envx, hls = 'hls.yml')
Здесь создаётся класс-наследник класса базового сборочного сценария, в конструкторе которого добавляется цель иерархической сборки, которая осуществляет компиляцию и запуск HLS csim
. Кроме этого, данная цель устанавливается явной зависимостью для цели запуска симулятора в консольном режиме, что будет при каждом прогоне симулятора сначала вызывать сборку цели csim
и ещё запуск. Ну, и в конструктор класса передаётся список для HLS описаний.
Управление условной сборкой🔗
Как было сказано выше, все три сборочных варианта реализуются из одного и того же дерева исходных файлов. Т.к. проект очень простой, то для описания основной структуры достаточно одного файла — это top.sv
. В нём описана вся необходимая инфраструктура, которая является общей для всех трёх СВ. К ней относятся входной буфер, описание тактового генератора (PLL) и формирователь выходного тактового сигнала (ODDR). Различия касаются реализации собственно сумматора. Для учёта этих различий служит параметр ADDER_MODULE
, который определён для СВ 7a50t
и ac701
(т.е. вариантов, которые используют реализацию через отдельный модуль) в их конфигурационных файлах main.yml
.
Помимо этого для всех трёх СВ требуется немного разный набор исходных файлов. Например, для 7a35t
никакие дополнительные исходные файлы не нужны, а для 7a50t
и ac701
нужен файл с описанием SystemVerilog интерфейсов, используемых в качестве портов внешнего модуля сумматора, и для каждого из 7a50t
и ac701
требуется свой собственный HDL файл-"обёртка", т.к. БД и HLS реализации сумматора дают существенно разные наборы портов модулей. Решить эту проблему можно достаточно легко, определив в каждом СВ свою версию конфигурационного файла-списка исходных файлов src_syn.yml
. Но существует более компактное и наглядное решение:
#
# src/cfg/common/env/src_syn.yml
#
import : main
parameters:
ADDER_SOURCE : = 'adder_bd.sv' if main.VARIANT_NAME == 'ac701' else
'adder_hls.sv' if main.VARIANT_NAME == '7a50t' else
None
ADDER_IF_SOURCE : = None if main.VARIANT_NAME == '7a35t' else 'adder_if.sv'
sources:
- src/syn/top.sv
- src/syn/$ADDER_SOURCE
- src/syn/$ADDER_IF_SOURCE
Здесь путём вычисляемых параметров и подстановкой их значений формируется корректный список исходных файлов для всех сборочных вариантов. Подробнее описано в документации на систему сборки.
Итог🔗
Описанные примеры реализации простого сумматора демонстрируют значительную мощь и гибкость системы сборки FPGA проектов, показывая, как достаточно легко организовать параметризованные сборочные варианты, реализуемые на основе единого дерева исходных файлов, не дублируя при этом ни код скриптов сборочных сценариев, ни конфигурационные файлы, ни файлы исходного кода. Базовый скрипт сборочного сценария является вполне универсальным и почти без изменений может применяться для сборки крупных проектов. Описанный пример является по сути шаблоном, который можно использовать для старта любого рабочего проекта.