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
и содержит настройку сборочного окружения и описание целей сборки. Более подробно см. Сценарии сборки.