Build Variants - fpga-lib/site_scons GitHub Wiki
Назначением сборочного варианта является автоматизированное создание исполнительного окружения, пригодного для выполнения непосредственной работы по проекту: запуск моделирования (с компиляцией всех необходимых библиотек), осуществление синтеза (с созданием IP ядер, проекта САПР FPGA).
Ключевым моментом сборочного варианта является концепция конфигурационных файлов. Цель этого подхода состоит в том, чтобы максимально упростить для пользователя процесс задания параметров проекта без ущерба для гибкости управления этим. В качестве формата для конфигурационных файлов выбран yaml, и т.к. целевым языком сценариев SCons является Python, то для работы с этим форматом выбран модуль https://pyyaml.org.
Формат yaml файла является незамысловатым и по сути в простой текстовой форме описывает структуры, которые напрямую транслируются в списки (list) или словари (dict) языка Python. Комментарии определяются наличием символа # и до конца строки.
Сборочная система поддерживает несколько типов конфигурационных файлов:
- параметры общего назначения;
- списки файлов;
- параметры конфигурации IP ядра;
- параметры HLS модулей.
Формат конфигурационного файла параметров общего назначения может содержать несколько разделов, например:
-
import(необязательный); -
options(необязательный); -
parameters(обязательный).
В разделе parameters задаются произвольные параметры проекта в формате key : value, например:
parameters:
VARIANT_NAME : 7a50t
PROJECT_NAME : = VARIANT_NAME # ссылка на параметр VARIANT_NAME, определённый выше
TOP_NAME : top
TESTBENCH_NAME : top_tb
DEVICE : xc7a50tftg256-1Особенностью реализации является то, что при указании значения параметра, можно ссылаться на вышеописанные параметры через специальный синтаксис: выражения, начинающиеся со знака =. Вообще, такие выражения не ограничиваются только лишь ссылками на другие параметры, но являются полноценными выражениями языка Python. Например, вполне допустимо следующее:
#
# clk.yml
#
import : board
parameters:
REF_CLK : = board.OSC_FREQ # MHz
MAIN_CLK : 125 # MHz
DIFF_REFCLK : = ''
CLK_FREQ : = int(REF_CLK*1e6)
CLK_PERIOD : = str(1e9/CLK_FREQ) + 'ns' #
CLK_HALF_PERIOD : = str(1e9/CLK_FREQ/2) + 'ns' #В этом примере помимо ссылок на параметры текущего файла используется ссылка на параметр другого файла, указанного в разделе import. Расширение импортируемого файла опускается, указывается только имя. Содержимое файла board.yml представляет собой:
#
# board.yml
#
parameters:
OSC_FREQ : 100 # MHz Результат обработки конфигурационных файлов, например, с целью получения заголовочного файла с параметрами на языке Verilog будет следующим:
...
`define REF_CLK 100
`define MAIN_CLK 125
`define DIFF_REFCLK
`define CLK_FREQ 100000000
`define CLK_PERIOD 10.0ns
`define CLK_HALF_PERIOD 5.0ns
...ЗАМЕЧАНИЕ. Строка:
DIFF_REFCLK : = '' демонстрирует способ корректно задавать пустые параметры. Это существенно для некоторых форматов — в частности, в генерируемых Tcl файлах такие параметры преобразуются в переменные со значением "пустая строка":
set DIFF_REFCLK ""Также существует специальный синтаксис для описания сток в кавычках, что бывает необходимым при определении макросов со строковыми значениями:
CAPABILITY_STRUCTURE_INIT_FILE : = '`'+ os.path.join(dirpath.BUILD, 'src', 'capstruct.vhex')+'`'что в результате даёт:
`define CAPABILITY_STRUCTURE_INIT_FILE "/opt/ssd/pro/katun2/build/kcu116/src/capstruct.vhex"Иногда необходимо генерировать макросы языка Verilog/SystemVerilog по условию: это возникает из-за того, что в этих HDL нет директивы препроцессора if, а есть только ifdef/ifndef, поэтому, например, для условной компиляции нельзя использовать значение макроса, а можно только его наличие или отсутствие. Для того, чтобы задать условную генерацию макроса в генерируемых HDL заголовочных файлах, используется специальное значение __NO_DEFINE__. Например:
#
# params.yml
#
import : clk
parameters:
DATA_WIDTH : 16
USE_REGISTER_SLICE : = 'yes' if clk.REF_CLK > 200 else '__NO_DEFINE__'
В этом примере макрос USE_REGISTER_SLICE будет присутствовать в сгенерированном заголовочном HDL файле только в случае, если параметр REF_CLK из конфигурационного файла clk.yml будет превышать значение 200 (МГц). В HDL коде можно применить условную компиляцию ifdef по наличию этого макроса.
В разделе import может быть указано несколько импортируемых файлов, имена которых перечисляются через пробел.
ВАЖНОЕ ЗАМЕЧАНИЕ. Особенностью реализации является то, что для импортированных конфигурационных файлов не требуется указывать путь к ним и расширение. Это сделано для удобства использования, чтобы не загромождать описание, а также чтобы не требовалось править конфигурационные файлы в случае их перемещения. Поиск конфигурационных файлов осуществляется по списку путей, которые задаются в сборочном скрипте с помощью служебной функции add_search_path(<path>|<path-list>). Поиск осуществляется в том порядке, в котором пути заданы. Например:
#
# Build variant script (SConscript/*.scons file)
#
...
add_search_path( os.path.join( os.getcwd(), 'env') )
add_search_path( os.path.join( os.getcwd(), os.pardir, 'common', 'env') )
...В этом случае поиск конфигурационных файлов будет осуществляться сначала в директории:
<build-variant-dir>/env,
затем в:
<build-variant-dir>/../common/env.
Это открывает широкие возможности по гибкому конфигурированию сборочных вариантов — например, в common/env находится конфигурационный файл clk.yml, являющийся общим для всех сборочных вариантов, кроме одного, в котором требуется иметь иные параметры, и это решается путём помещения в <build-variant-dir>/env другого варианта файла clk.yml, содержащего требуемые для этого сборочного варианта параметры. При обработке раздела import поисковая система первым обнаружит clk.yml внутри сборочного варианта, т.к. этот путь находится выше в списке путей поиска.
Таким образом, получается простая и эффективная схема задания параметров для сборочных вариантов, позволяющая вынести общие параметры в отдельное место (common в примере выше), и "перекрывать" эти общие параметры индивидуальными путём подмены конфигурационных файлов на основе порядка путей поиска.
Раздел options предназначен для расширения возможностей при обработке конфигурационных файлов. В частности, этот раздел может содержать параметры prefix и suffix, которые распознаются некоторыми билдерами и используются для формирования имён при генерировании включаемых файлов HDL и скриптов.
Формат файла конфигурации IP ядер содержит три раздела: import, type, config. Пример:
#
# pll.yml
#
import : clk
type : clk_wiz
config:
PRIMITIVE : PLL
PRIM_IN_FREQ : = clk.REF_CLK
CLKOUT1_REQUESTED_OUT_FREQ : = clk.MAIN_CLK
USE_LOCKED : true
USE_RESET : false
USE_SAFE_CLOCK_STARTUP : trueПравила действуют те же самые, что и для файлов параметров (импорт и подстановки-выражения). Раздел type необходим для указания типа IP ядра, а раздел config — это то, что определяет собственно свойства IP ядра.
Конфигурационный файл, содержащий список исходных файлов, содержит обязательный раздел sources, в котором перечислены исходные файлы с путями, указанными либо от текущего пути сборочного варианта, либо от корневой директории проекта — той, где находится файл SConstruct. Пример:
#
# src_syn.yml
#
sources:
- lib/pf.sv
- lib/axi.sv
- lib/axi/axi_data_shift.sv
- lib/axi/axi_rd_crossbar.sv
- lib/axi/axi_wr_crossbar.sv
- lib/fifo/fifo_sc.sv
- lib/fifo/fifo_dc.sv
- lib/mem/infer/block_ram.sv
- lib/mem/infer/distributed_ram.sv
- lib/mem/infer/distributed_rom.sv
- src/syn/top.sv
- src/syn/core.sv
- src/syn/task_loader.sv
- src/syn/task_manager.sv
- src/syn/pcie/axi4lite_adapter.sv
...Такие файлы могут содержать списки файлов любого типа: HDL, констрейны, конфигурационные файлы IP ядер и т.д. Как уже было сказано, пути в списках файлов могут быть заданы от корня проекта (как в примере выше) или от корня директории сборочного варианта. Второй способ предпочтительнее для конфигурационных файлов, использование которых локализовано в пределах текущего сборочного варианта, например:
#
# xdc.yml
#
sources:
- xdc/timing.xdc
- xdc/properties.xdc
- xdc/floorplan.xdcПри обработке файлов списков действует следующее правило формирования целевых путей:
- сначала формируется абсолютный путь по схеме
<build-variant-path>/<path-from-yml>и проверяется, существует ли файл по этому пути. Если существует, то полученный путь используется в дальнейшем. Если не существует, то: - формируется абсолютный путь по схеме
<project-root-path>/<path-from-yml>и далее аналогичная предыдущему пункту проверка. Если и по этому пути файла не существует, то сборка останавливается с ошибкой и выводом соответствующего сообщения.
Помимо обязательного раздела sources конфигурационный файл списка может содержать опциональный раздел parameters. Этот раздел является точно таким же, как и в файлах параметров с той разницей, что там он является обязательным, а тут опциональным. Этот раздел позволяет создавать локальные параметры для гибкого управления списками файлов. Например, когда нужно тот или иной исходный файл включать в сборку по условию или подменять на другой в зависимости от внешних параметров. Пример:
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В этом примере создаются два параметра, содержащие имена исходных файлов. Значения этих параметров зависят от текущего сборочного варианта. Далее в разделе sources осуществляется использование значений параметров через оператор подстановки $. Если подстановочное значение вычисляется до False (т.е. имеет значение 0, False, None, пустую строку), то текущая строка списка аннулируется — ничего из данной строки не передаётся далее.
В примере выше в качестве исходного файла модуля adder для сборочного варианта ac701 будет использоваться исходный файл adder_bd.sv, для сборочного варианта 7a50t — adder_hls.sv, для остальных сборочных вариантов этот модуль использоваться не будет. Второй параметр вызовет включение исходного файла adder_if.sv для всех сборочных вариантов кроме 7a35t.
Конфигурационный файл параметров HLS по сути является обычным конфигурационным файлом параметров за исключением того, что в обычном конфигурационном файле набор параметров произволен, а в конфигурационном файле HLS этот набор в значительной степени фиксирован. Как и в обычном конфигурационном файле параметров тут присутствует обязательный раздел parameters и опциональный import. Типовой конфигурационный файл HLS выглядит так:
import : main env clk
parameters:
name : adder
version : 1.0
vendor : slon
library : hls
clock_period : = str(1000/clk.MAIN_CLK) + 'ns'
clock_name : adder_clk
clock_uncertainty : 25%
cflags : = ' -g' + ' -DDATA_WIDTH=' + str(main.DATA_WIDTH)
csimflags : = ' -g' +
' -I' + os.path.join(env.QUESTABASE, 'include') +
' -I' + os.path.join(env.XILINX_HLS, 'include')
src_csyn_list : src_csyn.yml
src_csim_list : src_csim.yml
hook_list : directives.ymlПравила описания параметров, поиска импортируемых конфигурационных файлов и т.п. остаются теми же самыми, что и для остальных конфигурационных файлов.
Для работы с параметрами на уровне сценария сборочного варианта предназначена функция import_config(). Пример использования:
#
# main.yml
#
parameters:
VARIANT_NAME : 7a50t
PROJECT_NAME : = VARIANT_NAME # ссылка на параметр VARIANT_NAME, определённый выше
TOP_NAME : top
TESTBENCH_NAME : top_tb
DEVICE : xc7a50tftg256-1#
# <variant_name>.scons
#
...
cfg = import_config('main.yml')
...
env['VIVADO_PROJECT_NAME'] = cfg.PROJECT_NAME
env['TOP_NAME'] = cfg.TOP_NAME
env['TESTBENCH_NAME'] = cfg.TESTBENCH_NAME
env['DEVICE'] = cfg.DEVICE
...Как видно, нотация очень простая: функция возвращает объект, содержащий параметры, заданные в конфигурационном файле, с теми же именами.
Работать напрямую с конфигурационными файлами IP ядер на уровне сценария как правило не нужно, всю эту работу выполняют сборщики-билдеры (builders), пользователю достаточно указать только список файлов с описанием параметров IP ядер.
Для работы с файлами списков исходных файлов служит функция read_sources(). Она получает в качестве параметра имя конфигурационного файла со списком исходных файлов и возвращает список с абсолютными путями исходных файлов, перечисленных в конфигурационном файле:
src = read_sources('src_syn.yml')
src_sim = read_sources('src_sim.yml')
ip = read_sources('ip.yml')Полученные списки в дальнейшем используются в качестве зависимостей для построения сценариев сборки.
Сценарии сборки описываются в файле скрипта SCons, находящимся в корневой директории сборочного варианта — SConscript или <variant-name>.scons. Этот скрипт является файлом на языке Python и содержит настройку сборочного окружения и описание целей сборки. Более подробно см. Сценарии сборки.