Сценарии сборки - fpga-lib/site_scons GitHub Wiki
Сценарием сборки называется описание способов получения целей сборки из источников (как правило, файлов), которые называются зависимостями. Установление связей между целями и зависимостями осуществляется разными способами, основным из которых является использование билдеров (builders, см. ниже), а так же явное указание зависимости с помощью функции Depends()
. Помимо обычных (основных) целей существуют так называемые мнимые цели (phony targets), которые не являются файлами или директориями, а, как правило, обозначают действия — например, запуск какого-либо инструмента безусловно.
Смысл оформлять такие действия в виде целей состоит в том, что при этом появляется возможность обработать сопутствующие зависимости, например: при запуске симулятора на прогон теста нужно не забыть сгенерировать заголовочные файлы с параметрами, если были изменения параметров; при прямом запуске симулятора нужно помнить об этом и выполнять это действие вручную, а при наличии мнимой цели, осуществляющей запуск симулятора, можно поставить ей в зависимость эти заголовочные файлы, и система сборки сама автоматически будет производить генерирование всех нужных файлов в зависимости от того, изменились ли файлы с параметрами проекта, от которых зависят эти заголовочные файлы.
Сборочное окружение (СО) — это объект SCons
, содержащий всё необходимое для реализации сборки по требуемому сценарию. Основу СО составляют переменные сборочного окружения, которые содержат различные настройки, начиная от названий и путей внешних инструментов, опций их запуска, и заканчивая вспомогательными параметрами разного назначения (название проекта, расширения файлов, пути, определяющие структуру проекта и т.п.).
Помимо переменных сборочное окружение содержит набор билдеров (см. ниже), сканеров и других необходимых для осуществления процесса сборки объектов. Создаётся сборочное окружение с помощью конструктора специального объекта SCons
:
envx = Environment() # создание сборочного окружения по умолчанию, имя объекта может быть произвольным
Такое СО содержит набор билдеров и переменных по умолчанию, большинство из которых относится к миру традиционного ПО. Например, с помощью такого СО очень легко организовать сборку исполняемого файла из исходных файлов на языках C/C++
(билдер Program
, см. официальную документацию на SCons
). Можно было бы легко создать СО без этих переменных и билдеров, указав аргументом конструктору tools = { }
, но смысла в этом немного, а эти инструменты могут пригодиться в какой-либо ситуации, если внезапно потребуется собрать исполняемую программу — например, для Verilog/DPI.
Поведение инструментов (tools) можно легко изменить, указав соответствующие значения переменным окружения. То же самое относится и к специализированным инструментам, реализованным для поддержки работы SCons
с проектами на FPGA, речь о которых пойдёт ниже.
Доступ к переменными СО может осуществляться как непосредственно в нотации словаря языка Python
:
envx['TOP_NAME'] = 'top_level_module'
при которой производится присвоение значения переменной, так и с помощью специальных функций:
envx.Append(VLOG_FLAGS = ' -O5 -timescale=1ns/1ps')
...
envx.Append(USER_DEFINED_PARAMS = {'ROOT_DIR' : envx['ROOT_PATH']})
envx.Append(USER_DEFINED_PARAMS = {'CFG_DIR' : envx['CFG_PATH']})
envx.Append(USER_DEFINED_PARAMS = {'BUILD_SRC_DIR' : envx['BUILD_SRC_PATH']})
модифицирующих переменную, добавляя к ней новые объекты (переменная в этом случае является списком). Вместо Append()
можно использовать Prepend()
, которая добавляет аргументы не в конец, а в начало списка. Более подробную справочную информацию по способам работы с переменными сборочных окружений можно найти в официальной документации на SCons
.
Принципы, возможности и подходы, реализованные в сборочном инструментарии SCons
подробно и доходчиво описаны в официальной документации. Для облегчения понимания данного раздела ниже даётся краткое описание работы инструмента применительно к рассматриваемой системе сборки.
Работа SCons
выполняется в два этапа:
- чтение скриптов, начиная с корневого
SConstruct
и далее по иерархии сборки, выявление целей и зависимостей, построение графа зависимостей и определение того, какие действия необходимо выполнить для обеспечения актуальности целей; - запуск внешних или внутренних (скриптов) инструментов в требуемой последовательности для реализации плана действий, составленного в п.1.
Работа SCons
всегда начинается с чтения и анализа файла SConstruct
, в котором содержатся общие действия, такие как обработка аргументов командной строки, создание сборочного окружения (СО), установление значений (при необходимости) некоторых переменных СО, управление сборкой по иерархии — передача управления в SConscript
/*.scons
файлы сборочных вариантов, в которых описываются основные сценарии сборки.
При чтении SConscript
/*.scons
файла происходит переход в директорию сборочного варианта, которая с этого момента является текущей. Это обстоятельство используется для автоматического определения имени сборочного варианта, которое в дальнейшем служат для соответствующего именования исполнительного окружения в директории build
.
Следует отметить, что при этом происходит передача объекта сборочного окружения из корневого файла в файл сборочного варианта:
#
# SConstruct
#
...
SConscript(variant_path, exports='envx')
...
#
# SConscript/*.scons
#
Import('envx')
...
По окончании чтения и анализа SConscript
/*.scons
файла осуществляется переход к непосредственным действиям. При этом SCons
переходит в корневую директорию проекта (туда, где находится файл SConsctruct
), и вызов всех инструментов производится из этой директории. Учитывая вышеописанное и во избежание проблем с путями к файлам и директориям, которые будут непосредственно участвовать в процессах запусках инструментов, необходимо, чтобы пути были либо от корневой директории проекта, либо абсолютными. Сборочные сценарии и их компоненты (в частности, билдеры) должны учитывать эту особенность, что, впрочем, достигается без особых сложностей.
Билдер — это специальный объект SCons
, обеспечивающий связь цели и зависимости при построении графа зависимостей, а так же реализующий непосредственное действие, приводящее цель в актуальное состояние. Билдер имеет ассоциированную с ним исполнительную функцию (action function), которая собственно и отвечает за действия, направленные на приведение цели в актуальное состояние. Функция принимает три агрумента:
def action_function(target, source, env):
...
первые два из которых являются списками целей и источников (зависимостей) соответственно.
Конструктор билдера имеет два аргумента, а запуск билдера устанавливает непосредственную связь между целью и зависимостями:
trg = SomeBuilder(target, source)
Конструктор билдера возвращает список целей, который может быть использован в качестве зависимости в для другого билдера и т.д. Это позволяет выстраивать цепочки зависимостей от исходных файлов к промежуточным и финальным целям.
Использование билдера напрямую зачастую оказывается не очень подходящим. Например, имя цели удобно сформировать автоматически из имени зависимости либо оно (имя цели) определяется из действия в случае мнимых целей. Или другой случай: существует множество исходных файлов, для каждого из которых нужно создать соответствующую цель, т.е. по сути выполнить запуск билдера в цикле для каждой зависимости.
Для решения описанных задач SCons
предусматривает концепцию псевдобилдеров. Псевдобилдер — это по сути объект-"обёртка" вокруг настоящего билдера, осуществляющая все необходимые вспомогательные действия перед вызовом собственно билдера. Технически псевдобилдер представляет собой функцию языка Python
, содержащую два и более аргумента:
def pseudo_builder(env, src [,...]):
...
первым из которых является объект сборочного окружения, к которому относится псевдобилдер, вторым, как правило, список источников (зависимостей), остальные аргументы опциональны. Чтобы псевдобилдер был доступен при работе с сборочным окружением, его необходимо добавить к СО с помощью функции AddMethod()
. За дополнительными деталями можно обратиться к официальной документации.
Сценарии сборки рассматриваемой сборочной системы используют преимущественно псевдобилдеры.
Для реализации сборочных сценариев в системе сборки для работы над проектами с FPGA требуются специализированные билдеры (и псевдобилдеры), обеспечивающие создание и обновление промежуточных и финальных целей проекта на FPGA, таких как создание IP ядер, out-of-context компиляция IP ядер, компиляция симуляционных моделей IP ядер и компиляция рабочей библиотеки симулятора, создание проектов блочных дизайнов и их синтез, компиляция IP ядер из HLS описаний, создание проекта САПР FPGA, запуск моделирования и синтеза и т.д. Все эти действия выполняются по зависимостям и реализуются с помощью соответствующих билдеров.
Для того, чтобы не загромождать файл сборочного сценария текущего варианта, весь код билдеров, псевдобилдеров, сканеров, переменных СО и ряда вспомогательных функций вынесен в специальные файлы или пакеты языка Python
, называемые в терминологии SCons
инструментами — tools
. Рассматриваемая система сборки в настоящий момент поддерживает два инструмента: vivado
и questa
, для синтеза и моделирования соответственно. Перечень доступных средств упомянутых инструментов кратко описан в разделе Reference.
Подключение инструмента к сборочному сценарию осуществляется с помощью объекта 'Tool':
#
# <variant-name>.scons
#
Import('envx')
envx.Tool('vivado')
envx.Tool('questa')
после чего можно пользоваться всеми средствами инструментов, начиная от изменения значений переменных СО, заданных инструментами по умолчанию, и заканчивая построением непосредственно сценария сборки, устанавливая связь "цель→зависимость" с помощью билдеров (псевдобилдеров).
Сценарии сборки строятся приблизительно по одной схеме, различия касаются в основном каких-то индивидуальных потребностей того или иного сборочного варианта. Общая схема файла сценария сборки выглядит так:
- подготовка параметров проекта (сборочного варианта) для дальнейшего использования;
- настройка сборочного окружения;
- подготовка списков исходных файлов;
- описание целей:
- цели по зависимостям;
- указание явных зависимостей;
- дополнительные настройки (безусловная сборка, цели по умолчанию);
- псевдонимы целей.
Схема не является жёсткой, вполне допустимы различные отклонения. В частности, как правило, не важно, что идёт сначала — настройка сборочного окружения или подготовка списков исходных файлов, эти части можно описывать в любом порядке.
Подготовка параметров сводится к чтению файлов параметров (см. Конфигурационные файлы) и созданию внутренних объектов, которые являются по сути контейнерами этих параметров:
cfg = import_config('main.yml')
dirs = import_config('dirpath.yml')
Технически объекты cfg
, dirs
являются классами Python
, что позволяет работать с ними в удобной нотации <object>.<name>
.
Здесь выполняется основная работа по подготовке и настройке внутреннего инструментария сборки:
Import('envx')
# tools
envx.Tool('vivado')
envx.Tool('questa')
# some project parameters
envx['VIVADO_PROJECT_NAME'] = cfg.PROJECT_NAME
envx['TOP_NAME'] = cfg.TOP_NAME
envx['TESTBENCH_NAME'] = cfg.TESTBENCH_NAME
envx['DEVICE'] = cfg.DEVICE
# path
envx['SETTINGS_SEARCH_PATH'] = [dirs.SETTINGS] # dirs for setting files (typically *.yml) need for import scanners
envx['INC_PATH'] = envx['BUILD_SRC_PATH']
envx['SIM_INC_PATH'] = envx['INC_PATH']
# simulator invocation flags
envx.Append(VLOG_FLAGS = ' -O5 -timescale=1ns/1ps')
envx.Append(VOPT_FLAGS = ' -O5 -L wlib -L unifast_ver -L unisims_ver -L unimacro_ver -L secureip -L xpmlib -L ipsimlib')
# user-defined parameters
envx.Append(USER_DEFINED_PARAMS = {'ROOT_DIR' : envx['ROOT_PATH']})
envx.Append(USER_DEFINED_PARAMS = {'CFG_DIR' : envx['CFG_PATH']})
envx.Append(USER_DEFINED_PARAMS = {'BUILD_SRC_DIR' : envx['BUILD_SRC_PATH']})
...
Подготовка списков исходных файлов — это чтение конфигурационных файлов со списками и формирование списков языка Python
, составляющих зависимости для различных целей, например:
src = read_sources('src_syn.yml')
src_sim = read_sources('src_sim.yml')
ip = read_sources('ip.yml')
src_par = 'main.yml clk.yml'
src_xpr = ['src_syn.yml', 'xdc.yml', 'xpr_hook.tcl']
В этом примере создаются разные списки исходных файлов для описания различных целей. Списки создаются как с помощью чтения конфигурационных файлов (src
, src_sim
, ip
), так и вручную (src_par
, src_xpr
). Поддерживается строковое описание (src_par
) и в виде списка ЯП Python
(src_xpr
).
Описание целей технически является простой задачей — просто указать зависимости с помощью билдеров. Но в действительности это самая сложная часть, она требует хорошего понимания, что из чего строится и от чего зависит. Например, цель "Создание проекта Vivado" зависит от src_xpr
(см. пример выше) и от IP ядер, т.е. если поменялся один из файлов в списке src_xpr
или обновилось хотя бы одно IP ядро, то проект Vivado будет пересоздан. Но помимо явных зависимостей есть ещё неявные, изменение которых тоже влияет на цель. К таким неявным зависимостям относятся, в частности, файлы, создаваемые из параметров — в данном примере, это заголовочный файл Verilog/SystemVerilog
для HDL и Tcl
для скриптов, запускаемых во время создания проекта Vivado.
...
# scripts
IP_Create_Scripts = envx.IpCreateScripts(ip)
IP_Cores = envx.CreateIps(IP_Create_Scripts)
...
# generated sources
CfgParamsHeader = envx.CreateCfgParamsHeader(os.path.join(envx['BUILD_SRC_PATH'], 'cfg_params.svh'), src_par)
CfgParamsTcl = envx.CreateCfgParamsTcl(os.path.join(envx['BUILD_SRC_PATH'], 'cfg_params.tcl'), 'params.yml')
...
VivadoProject = envx.CreateVivadoProject(src_xpr , IP_Cores)
...
Depends(VivadoProject, [CfgParamsHeader, CfgParamsTcl])
...
В этом примере показаны все зависимости, необходимые для создания цели VivadoProject
. Указание явной зависимости через Depends()
вынудит SCons
проверить зависимость промежуточных целей CfgParamsHeader
и CfgParamsTcl
от их источников, которыми являются конфигурационные файлы с параметрами проекта. Таким образом, если, например, изменён какой-то файл с параметром, влияющий на любой из этих генерируемых файлов, то при запуске сборки цели VivadoProject
эти промежуточные файлы будут обновлены перед запуском обновления основной цели.
Это один из вариантов решения. Цель примера — проиллюстрировать приблизительный путь работы с целями и зависимостями.
Для удобства использования при запуске SCons
предусмотрена возможность создавать псевдонимы целей:
envx.Alias('cparam', CfgParamsHeader)
envx.Alias('cparam-tcl', [CfgParamsTcl])
envx.Alias('xpr', VivadoProject)
Теперь достаточно указать имя псевдонима в командной строке запуска SCons
.
Рабочий пример сборочного сценария можно найти в тестовом проекте.