Сборочный вариант🔗
Назначение🔗
Назначением сборочного варианта является автоматизированное создание исполнительного окружения, пригодного для выполнения непосредственной работы по проекту: запуск моделирования (с компиляцией всех необходимых библиотек), осуществление синтеза (с созданием IP ядер, проекта САПР FPGA).
Реализация🔗
Конфигурационные файлы🔗
Ключевым моментом сборочного варианта является концепция конфигурационных файлов. Цель этого подхода состоит в том, чтобы максимально упростить для пользователя процесс задания параметров проекта без ущерба для гибкости управления этим. В качестве формата для конфигурационных файлов выбран yaml, и т.к. целевым языком сценариев SCons является Python, то для работы с этим форматом используется модуль pyyaml.
Формат yaml
файла является незамысловатым и по сути в простой текстовой форме описывает структуры, которые напрямую транслируются в списки (list) или словари (dict) языка Python. Комментарии определяются наличием символа #
и до конца строки.
Сборочная система поддерживает несколько типов конфигурационных файлов:
- параметры общего назначения;
- списки файлов;
- параметры конфигурации IP ядра;
- параметры HLS модулей.
Конфигурационный файл параметров общего назначения🔗
Формат конфигурационного файла параметров общего назначения может содержать несколько разделов, например:
import
(необязательный);load
(необязательный);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
представляет собой:
Результат обработки конфигурационных файлов, например, с целью получения заголовочного файла с параметрами на языке 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
...
Помимо заголовочного файла с макросами создаётся ещё заголовочный файл с этими же параметрами в формате SystemVerilog package:
package cfg_params_pkg;
...
localparam int REF_CLK = 100;
localparam int MAIN_CLK = 125;
localparam string REF_CLK_PERIOD = "10.0ns";
localparam string REF_CLK_HALF_PERIOD = "5.0ns";
...
endpackage : cfg_params_pkg_pkg
Такой формат может оказаться предпочтительнее для использования во многих случаях благодаря типизации параметров. К сожалению, он не может полностью заменить функциональность макросов (например, использование условной компиляции), поэтому генерируется оба варианта, каждый из которых может применяться по выбору пользователя.
Для параметров поддерживается три типа:
- целый знаковый
int
; - с плавающей точкой
real
; - строковый
string
.
Типы параметров используются в package и выводятся из типов параметров конфигурационных (yaml) файлов.
ЗАМЕЧАНИЕ
Строка:
демонстрирует способ корректно задавать пустые параметры. Это существенно для некоторых форматов — в частности, в генерируемых Tcl
файлах такие параметры преобразуются в переменные со значением "пустая строка":
Также существует специальный синтаксис для описания сток в кавычках, что бывает необходимым при определении макросов со строковыми значениями:
что в результате даёт:
Иногда необходимо генерировать макросы языка 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
в примере выше), и "перекрывать" эти общие параметры индивидуальными путём подмены конфигурационных файлов на основе порядка путей поиска.
Раздел load
предназначен для возможности загрузки модулей языка Python, код которых может быть использован при вычислении значений параметров. Например:
load: ext_util
...
parameters:
...
IP_ADDRESS : '192.168.10.10'
...
IP_ADDR : = ext_util.ip_hex(IP_ADDRESS)
# ext_util.py
def ip_hex(ip : str) -> str:
return "32'h" + ''.join([format((int(i)), '02x') for i in ip.split('.')])
После обработки конфигурационного файла значение параметра IP_ADDR
будет равно 32'hc0a80a0a
, и его можно использовать в исходном коде Verilog/SystemVerilog
.
Файл ext_util.py
должен быть размещён в одной из директорий, которые добавлены в список путей поиска командами add_search_path
.
Раздел options
предназначен для расширения возможностей при обработке конфигурационных файлов. В частности, этот раздел может содержать параметры prefix
и suffix
, которые распознаются некоторыми билдерами и используются для формирования имён при генерировании включаемых файлов HDL и скриптов.
Конфигурационный файл параметров IP ядра🔗
Формат файла конфигурации 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 ядер и т.д. Как уже было сказано, пути в списках файлов могут быть заданы от корня проекта (как в примере выше) или от корня директории сборочного варианта. Второй способ предпочтительнее для конфигурационных файлов, использование которых локализовано в пределах текущего сборочного варианта, например:
При обработке файлов списков действует следующее правило формирования целевых путей:
- сначала формируется абсолютный путь по схеме
<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 по сути является обычным конфигурационным файлом параметров за исключением того, что в обычном конфигурационном файле набор параметров произволен, а в конфигурационном файле 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 ядер🔗
Работать напрямую с конфигурационными файлами IP ядер на уровне сценария как правило не нужно, всю эту работу выполняют сборщики-билдеры (builders), пользователю достаточно указать только список файлов с описанием параметров IP ядер.
Конфигурационные файлы списков🔗
Для работы с файлами списков исходных файлов служит функция read_sources()
. Она получает в качестве параметра имя конфигурационного файла со списком исходных файлов и возвращает список с абсолютными путями исходных файлов, перечисленных в конфигурационном файле:
Сценарии сборки🔗
Сценарии сборки описываются в файле скрипта SCons, находящемся в корневой директории сборочного варианта — SConscript
или <variant-name>.scons
. Этот скрипт является файлом на языке Python и содержит настройку сборочного окружения и описание целей сборки. Более подробно см. Сценарии сборки.