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 ядра

Формат файла конфигурации 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

При обработке файлов списков действует следующее правило формирования целевых путей:

  1. сначала формируется абсолютный путь по схеме <build-variant-path>/<path-from-yml> и проверяется, существует ли файл по этому пути. Если существует, то полученный путь используется в дальнейшем. Если не существует, то:
  2. формируется абсолютный путь по схеме <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, для сборочного варианта 7a50tadder_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(). Она получает в качестве параметра имя конфигурационного файла со списком исходных файлов и возвращает список с абсолютными путями исходных файлов, перечисленных в конфигурационном файле:

src     = read_sources('src_syn.yml')
src_sim = read_sources('src_sim.yml')
ip      = read_sources('ip.yml')

Полученные списки в дальнейшем используются в качестве зависимостей для построения сценариев сборки.

Сценарии сборки

Сценарии сборки описываются в файле скрипта SCons, находящимся в корневой директории сборочного варианта — SConscript или <variant-name>.scons. Этот скрипт является файлом на языке Python и содержит настройку сборочного окружения и описание целей сборки. Более подробно см. Сценарии сборки.



⚠️ **GitHub.com Fallback** ⚠️