Разработка под программируемые логические интегральные схемы (ПЛИС) и систем на кристалле (СНК) отличается монструозностью IDE и их проектов. В одном котле замешаны исходные коды логических модулей, специфические файлы для привязки к контретной модели ПЛИС, файлы ресурсов, тесты, скрипты сборки, IP-ядра, программы для процессорной системы и т.д. Всё это помножается на проприетарность инструментов, жесткие правила лицензирования и широкое использование бинарных форматов файлов.

Например, проект навигационного приемника под Xilinx Spartan 6, собираемый в уже устаревшей IDE Xilinx ISE, на диске занимает около 5 Гб. При этом большая часть файлов обновляется при любой манипуляции в IDE, часть из файлов - бинарные. Одним словом - ад для систем контроля версий. Заставить разработчика хранить файлы в репозитории было очень тяжело. А "чего нет в гите, того не существует". Без систем контроля версий ломаются все процессы разработки: от работы командой до тестирования.

К счастью, в современной среде Vivado разработчики сделали работу над ошибками. Отделить в проекте разрабатываемое человеком от генерируемого стало проще, появились механизмы сборки скриптами. Наши проекты окончательно перешли в git, а процессы разработки под ПЛИС перестали отличаться от процессов разработки программного обеспечения.

Эта статья написана в продолжение рассказа про организацию автотестирования радиоаппаратуры и отвечает на вопрос "как вы подготовили проект FPGA для хранения в репозитории и автоматической сборки в контейнере?". Она составлена по материалам пятилетней давности, а сам подход выдержал проверку временем.

Требования и пожелания

Чего хотим мы?

  • Иметь возможность откатиться к старой версии проекта.

  • Разворачивать и собирать проект на любой машине на основе одного-двух-N репозиториев.

  • Делать частые коммиты без страха за разрастающийся размер репозитория, т.е. коммититься должны файлы малого размера, рукописные, а не сгенерированные программой.

  • Легко переключаться между ветками.

  • Иметь возможность собирать прошивку без использования графического интерфейса для последующей автоматизации.

Особенности наших проектов:

  • Используется Xilinx Zynq, т.е. помимо части с программируемой логикой нужна и прошивка процессорной системы.

  • В разных дизайнах используются одни и те же модули (оформленные в виде HDL, а не IP блоков).

  • Используются родные Xilinx'овские IP модули (сериалайзеры, буферы, шины, сбросы и т.п.).

Project и non-Project workflow

Создатели Vivado выделяют два подхода к ведению проекта: project и non-project.

В первом случае мы активно пользуемся GUI, имеем файл проекта .xpr. Во втором - делаем упор на сборку на основе tcl-скриптов, т.е. реализуем unix-way.

Казалось бы вот оно решение - использовать non-project подход, отличный задел для автоматизации. Но на практике разработчики его отторгают. Людям привычнее и быстрее работать в GUI.

По этой причине мы остановились на смешанном подходе, когда непосредственно написание и отладка кода происходит в GUI, а разворачивание проекта при выгрузке из репозитория и автоматическая сборка - с помощью tcl-скриптов.

Содержимое проекта

Типы файлов, которые Vivado относит к исходным:

  • HDL and netlist files: Verilog (.v), SystemVerilog (.sv), VHDL (.vhd), and EDIF (.edf)

  • C based source files (.c)

  • Tcl files, run scripts, and init.tcl (.tcl)

  • Logical and Physical Constraints (.xdc)

  • IP core files (.xci)

  • IP core container (.xcix)

  • IP integrator block design files (.bd)

  • Design Checkpoint files (.dcp)

  • System Generator subsystems (.sgp)

  • Side files for use by related tools (например, “do”-файлы для симулятора)

  • Block Memory Map files (.bmm, .mmi)

  • Executable and Linkable Format files (.elf)

  • Coefficient files (.coe)

Что из этого списка хранить в репозитории? Что "рукописного" мы вносим в проект?

  • Модули на языке Verilog (.v) и SystemVerilog (.sv, .shv)

  • Testbench'и (_tb.v, _tb.sv)

  • Входные тестовые выборки для тестов

  • Настройки Xilinx'овских модулей в Block Diagram (.bd)

  • Описание ограничений (.xdc)

  • Описание портов и их соединение с сигналами модулей (.xdc)

  • Коэффициенты фильтров (.coe)

  • Настройки экранов в симуляторах (.wcfg)

Разделение песочницы и исходных файлов

Когда мы создаем новый дизайн в Vivado, то получаем по-умолчанию структуру каталогов, в которой перемешаны генерируемые и исходные файлы. Есть директории .srcs, .cache, .runs, .data, .hw, .ip_users_files, .sim и т.д. Идея заключается в том, чтобы выкинуть вовне исходные файлы и держать их в системе контроля версий, а в каталоге проекта оставить только генерируемые:

При выделении исходных файлов в отдельные каталоги конечная структура определяется разработчиком исходя из собственных предпочтений. В репозиторий кладутся только исходные файлы и tcl-скрипт, который позволяет воссоздать каталоги песочницы с генерируемыми файлами. Например:

Те логические модули, что используются в разных проектах (прошивках разных устройств), вынесены в отдельные сабмодули и подключаются наподобии библиотек. Они при этом являются самостоятельными Vivado-проектами и могут разрабатываться (в том числе тестироваться) независимо.

Блок-дизайны для различных плат вынесены в отдельную директорию bd. Там хранятся непосредственно .bd-файлы, которые являются текстовыми xml-файлами. Вспомогательные бинарные файлы генерируются из них уже вивадой.

К сожалению, вспомогательные файлы блок-дизайна генерируются в каталоге с bd-файлом, поэтому их приходится добавлять в игнор:

~/Oryx/src/fpga/.gitignore
# Always ignore journal and log files
*.log
*.jou
*.str

# Ignore trash in bd directory except .BD files
/bd/**/*
!/bd/**/*.bd

# Ignore editor's temporary files
*~
*#

# Ignore sendboxes
/prj*/

# Ignore Vivado temporary files
.Xil/

Шаг 1. Получаем исходные файлы

Рассмотрим процесс работы с таким проектом на примере типичной задачи: получение исходных кодов, внесение изменений, сборка bitstream-файла, прошивка устройства.

Первым шагом получим исходные коды из репозитория. Заводим каталог:

korogodin@Diod:~/$ mkdir Oryx
korogodin@Diod:~/$ cd Oryx

Мы готовы клонировать git-репозиторий. Для этого потребуется аккаунт и права доступа к проекту:

korogodin@Diod:~/Oryx$ git clone ssh://git@krgd.ru:123/git/src
Cloning into 'src'...
remote: Counting objects: 20490, done.
remote: Compressing objects: 100% (8449/8449), done.
remote: Total 20490 (delta 13181), reused 18342 (delta 11414)
Receiving objects: 100% (20490/20490), 787.09 MiB | 143.00 KiB/s, done.
Resolving deltas: 100% (13181/13181), done.
Checking connectivity... готово.

Среди прочего, мы получили желанные исходные файлы прошивки PL-части нашего СНК. Они расположены в каталоге fpga:

korogodin@Diod:~/$ cd src/fpga
korogodin@Diod:~/Oryx/src/fpga$ ls
bd  constr  prj_somz.tcl  sub  verilog

Но если присмотреться, то можно заметить, что каталоги подмодулей всё ещё пусты. Получаем их ревизии, соответствующие выбранной ветке в склонированном репозитории:

korogodin@Diod:~/Oryx/src/fpga$ git submodule update --init
korogodin@Diod:~/Oryx/src/fpga$ tree -L 2 sub
.
├── acquisition
│   ├── bin
│   ├── doc
│   ├── IPgen
│   ├── matlab
│   ├── sdk
│   ├── tb
│   └── verilog
├── correlator
│   ├── tb
│   └── verilog
├── dsp
│   ├── tb
│   └── verilog
├── serializer_zynq
│   └── verilog
└── sync
    └── verilog

В дереве каталогов есть все нужные для сборки прошивки исходные файлы.

Шаг 2. Разворачиваем проект Vivado

Чтобы запустить графический интерфейс Vivado и работать через него с нашими исходными кодами, мы должны развернуть Vivado-проект, т.е. создать xpr-файл и структуру временных каталогов. Делает это отдельный tcl-скрипт проекта, который можно сгенерировать из GUI (да-да, тут курица и яйцо).

Пример скрипта регенерации проекта
#!/usr/bin/tclsh
#
# Vivado (TM) v2015.3 (64-bit)
#
# prj_somz.tcl: Tcl script for re-creating project 'somz'
#
# Generated by Vivado on Tue Mar 22 10:11:05 +0300 2016
# IP Build 1367837 on Mon Sep 28 08:56:14 MDT 2015
#
# This file contains the Vivado Tcl commands for re-creating the project to the state*
# when this script was generated. In order to re-create the project, please source this
# file in the Vivado Tcl Shell.
#
# * Note that the runs in the created project will be configured the same way as the
#   original project, however they will not be launched automatically. To regenerate the
#   run results please launch the synthesis/implementation runs as needed.
#

# Set the reference directory for source file relative paths (by default the value is script directory path)
set origin_dir          "."
set sub_dir             "sub"
set prj_name            "somz"
set prj_dir_name        "prj_somz"
set topmodule_name      "mainboard_facq"

# Acquisition Microblaze firmware
set facq_prj_name       "mcs_facq"
set facq_bsp_name       "mcs_facq_bsp"
set facq_proc_name      "microblaze_0"
set hw_platform_name    "$topmodule_name\_hw_platform_0"


# Use origin directory path location variable, if specified in the tcl shell
if { [info exists ::origin_dir_loc] } {
  set origin_dir $::origin_dir_loc
}

variable script_file
set script_file "prj_$prj_name.tcl"

# Help information for this script
proc help {} {
  variable script_file
  puts "\nDescription:"
  puts "Recreate a Vivado project from this script. The created project will be"
  puts "functionally equivalent to the original project for which this script was"
  puts "generated. The script contains commands for creating a project, filesets,"
  puts "runs, adding/importing sources and setting properties on various objects.\n"
  puts "Syntax:"
  puts "$script_file"
  puts "$script_file -tclargs \[--origin_dir <path>\]"
  puts "$script_file -tclargs \[--help\]\n"
  puts "Usage:"
  puts "Name                   Description"
  puts "-------------------------------------------------------------------------"
  puts "\[--origin_dir <path>\]  Determine source file paths wrt this path. Default"
  puts "                       origin_dir path value is \".\", otherwise, the value"
  puts "                       that was set with the \"-paths_relative_to\" switch"
  puts "                       when this script was generated.\n"
  puts "\[--help\]               Print help information for this script"
  puts "-------------------------------------------------------------------------\n"
  exit 0
}

if { $::argc > 0 } {
  for {set i 0} {$i < [llength $::argc]} {incr i} {
    set option [string trim [lindex $::argv $i]]
    switch -regexp -- $option {
      "--origin_dir" { incr i; set origin_dir [lindex $::argv $i] }
      "--help"       { help }
      default {
        if { [regexp {^-} $option] } {
          puts "ERROR: Unknown option '$option' specified, please type '$script_file -tclargs --help' for usage info.\n"
          return 1
        }
      }
    }
  }
}

# Set the directory path for the original project from where this script was exported
#set orig_proj_dir "[file normalize "$origin_dir/mainboard_facq_release"]"

# Create project
create_project $prj_name ./$prj_dir_name

# Set the directory path for the new project
set proj_dir [get_property directory [current_project]]

# Set project properties
set obj [get_projects $prj_name]
set_property "default_lib" "xil_defaultlib" $obj
set_property "part" "xc7z045fbg676-2" $obj
set_property "sim.ip.auto_export_scripts" "1" $obj
set_property "simulator_language" "Mixed" $obj
set_property "source_mgmt_mode" "DisplayOnly" $obj

# Create 'sources_1' fileset (if not found)
if {[string equal [get_filesets -quiet sources_1] ""]} {
  create_fileset -srcset sources_1
}

# Set 'sources_1' fileset object
set obj [get_filesets sources_1]
set files [list \
 "[file normalize "$origin_dir/verilog/bus_interface.v"]"\
 "[file normalize "$origin_dir/verilog/$topmodule_name.v"]"\
 "[file normalize "$origin_dir/$sub_dir/serializer_zynq/verilog/zynq_deser_main.v"]"\
 "[file normalize "$origin_dir/$sub_dir/serializer_zynq/verilog/gearbox_4_to_7.v"]"\
 "[file normalize "$origin_dir/$sub_dir/serializer_zynq/verilog/n_x_serdes_1_to_7_mmcm_idelay_ddr.v"]"\
 "[file normalize "$origin_dir/$sub_dir/serializer_zynq/verilog/serdes_1_to_7_slave_idelay_ddr.v"]"\
 "[file normalize "$origin_dir/$sub_dir/serializer_zynq/verilog/serdes_1_to_7_mmcm_idelay_ddr.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/correlator_common.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/common_param.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/flag_sync_n.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_adder.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/correlator.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_sin_table.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_synthesizer.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_param.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_cos_table.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/correlator_channel.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_delay_reg.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/signal_mux_adc.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/common_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/common_timegen.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_shift_reg.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_cmplx_table.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/time_generator.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/channel_regfile.v"]"\
 "[file normalize "$origin_dir/$sub_dir/correlator/verilog/common_regfile.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/ed_det.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/conv_reg.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/signal_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/data_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/latency.v"]"\
 "[file normalize "$origin_dir/$sub_dir/sync/verilog/level_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/bin/$facq_prj_name.elf"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/DDS_I_Q.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/quant_level_table.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/arg_max.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/bram_dat_out_mux.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/poisk_IP.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/dat_in_sign_conv.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/doppler_dds.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/bram_addr_mux.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/pre_ader_adaptive.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/multi_sum.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/abs.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/lim_qnt.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/CORE.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/dop_shifter.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/sum_1_step.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/bram_multi_controller.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/poisk_time_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/bram_block.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/reset_poisk_sync.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/accum.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/acq_regfile.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/psp_rep_dds.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/bram_dat_mux.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/adaptive_quantizer.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/multi_core_correlator_conveer.v"]"\
 "[file normalize "$origin_dir/$sub_dir/acquisition/verilog/main/facq_ecpu.v"]"\
 "[file normalize "$origin_dir/$sub_dir/dsp/verilog/hist_sig_mag.v"]"\
]
add_files -norecurse -fileset $obj $files

# Set 'sources_1' fileset file properties for remote files
# None

# Set 'sources_1' fileset file properties for local files
set file "$origin_dir/$sub_dir/acquisition/bin/$facq_prj_name.elf"
set file_obj [get_files -of_objects [get_filesets sources_1] [list "$file"]]
set_property "scoped_to_cells" "microblaze_0" $file_obj
set_property "scoped_to_ref" "zynq" $file_obj
set_property "used_in" "implementation" $file_obj
set_property "used_in_simulation" "0" $file_obj


# Set 'sources_1' fileset properties
set obj [get_filesets sources_1]
set_property "include_dirs" "$origin_dir/$sub_dir/correlator/verilog $origin_dir/$sub_dir/acquisition/verilog/inc $origin_dir/verilog" $obj
set_property "top" "$topmodule_name" $obj

# Create 'zynq' fileset (if not found)
if {[string equal [get_filesets -quiet zynq] ""]} {
  create_fileset -blockset zynq
}

# Set 'zynq' fileset object
set obj [get_filesets zynq]
set files [list \
 "[file normalize "$origin_dir/bd/$prj_name/zynq.bd"]"\
]
add_files -norecurse -fileset $obj $files

# Set 'zynq' fileset file properties for remote files
set file "$origin_dir/bd/$prj_name/zynq.bd"
set file [file normalize $file]
set file_obj [get_files -of_objects [get_filesets zynq] [list "*$file"]]
if { ![get_property "is_locked" $file_obj] } {
  set_property "synth_checkpoint_mode" "Singular" $file_obj
}


# Set 'zynq' fileset properties
set obj [get_filesets zynq]
set_property "include_dirs" "$origin_dir/$sub_dir/correlator/verilog $origin_dir/verilog $origin_dir/$sub_dir/acquisition/verilog/inc" $obj
set_property "top" "zynq" $obj

# Create 'constrs_1' fileset (if not found)
if {[string equal [get_filesets -quiet constrs_1] ""]} {
  create_fileset -constrset constrs_1
}

# Set 'constrs_1' fileset object
set obj [get_filesets constrs_1]

# Add/Import constrs file and set constrs file properties
set file "[file normalize "$origin_dir/constr/$topmodule_name.xdc"]"
set file_added [add_files -norecurse -fileset $obj $file]
set file "$origin_dir/constr/$topmodule_name.xdc"
set file [file normalize $file]
set file_obj [get_files -of_objects [get_filesets constrs_1] [list "*$file"]]
set_property "file_type" "XDC" $file_obj

# Set 'constrs_1' fileset properties
set obj [get_filesets constrs_1]
set_property "target_constrs_file" "[file normalize "$origin_dir/constr/$topmodule_name.xdc"]" $obj

# Create 'sim_1' fileset (if not found)
if {[string equal [get_filesets -quiet sim_1] ""]} {
  create_fileset -simset sim_1
}

# Set 'sim_1' fileset object
set obj [get_filesets sim_1]
# Empty (no sources present)

# Set 'sim_1' fileset properties
set obj [get_filesets sim_1]
set_property "source_set" "" $obj
set_property "top" "$topmodule_name" $obj
set_property "xelab.nosort" "1" $obj
set_property "xelab.unifast" "" $obj

# Create 'synth_1' run (if not found)
if {[string equal [get_runs -quiet synth_1] ""]} {
  create_run -name synth_1 -part xc7z045fbg676-2 -flow {Vivado Synthesis 2014} -strategy "Vivado Synthesis Defaults" -constrset constrs_1
} else {
  set_property strategy "Vivado Synthesis Defaults" [get_runs synth_1]
  set_property flow "Vivado Synthesis 2014" [get_runs synth_1]
}
set obj [get_runs synth_1]
set_property "part" "xc7z045fbg676-2" $obj

# Create 'zynq_synth_1' run (if not found)
if {[string equal [get_runs -quiet zynq_synth_1] ""]} {
  create_run -name zynq_synth_1 -part xc7z045fbg676-2 -flow {Vivado Synthesis 2014} -strategy "Vivado Synthesis Defaults" -constrset zynq
} else {
  set_property strategy "Vivado Synthesis Defaults" [get_runs zynq_synth_1]
  set_property flow "Vivado Synthesis 2014" [get_runs zynq_synth_1]
}
set obj [get_runs zynq_synth_1]
set_property "constrset" "zynq" $obj
set_property "part" "xc7z045fbg676-2" $obj

# set the current synth run
current_run -synthesis [get_runs synth_1]

# Create 'impl_2' run (if not found)
if {[string equal [get_runs -quiet impl_2] ""]} {
  create_run -name impl_2 -part xc7z045fbg676-2 -flow {Vivado Implementation 2014} -strategy "Performance_Explore" -constrset constrs_1 -parent_run synth_1
} else {
  set_property strategy "Performance_Explore" [get_runs impl_2]
  set_property flow "Vivado Implementation 2014" [get_runs impl_2]
}
set obj [get_runs impl_2]
set_property "part" "xc7z045fbg676-2" $obj
set_property "steps.opt_design.args.directive" "Explore" $obj
set_property "steps.place_design.args.directive" "Explore" $obj
set_property "steps.phys_opt_design.is_enabled" "1" $obj
set_property "steps.phys_opt_design.args.directive" "Explore" $obj
set_property "steps.route_design.args.directive" "Explore" $obj
set_property "steps.write_bitstream.args.readback_file" "0" $obj
set_property "steps.write_bitstream.args.verbose" "0" $obj

# Create 'zynq_impl_1' run (if not found)
if {[string equal [get_runs -quiet zynq_impl_1] ""]} {
  create_run -name zynq_impl_1 -part xc7z045fbg676-2 -flow {Vivado Implementation 2014} -strategy "Vivado Implementation Defaults" -constrset zynq -parent_run zynq_synth_1
} else {
  set_property strategy "Vivado Implementation Defaults" [get_runs zynq_impl_1]
  set_property flow "Vivado Implementation 2014" [get_runs zynq_impl_1]
}
set obj [get_runs zynq_impl_1]
set_property "constrset" "zynq" $obj
set_property "part" "xc7z045fbg676-2" $obj
set_property "steps.write_bitstream.args.readback_file" "0" $obj
set_property "steps.write_bitstream.args.verbose" "0" $obj

# set the current impl run
current_run -implementation [get_runs impl_2]

puts "INFO: Project created:somz"

puts "INFO: Generate all targets from BD"
set file "$origin_dir/bd/$prj_name/zynq.bd"
set file [file normalize $file]
generate_target all [get_files  $file]

puts "INFO: Export HW"
file mkdir [file normalize "$proj_dir/$prj_name.sdk"]
write_hwdef -force  -file [file normalize "$proj_dir/$prj_name.sdk/$topmodule_name.hdf"]

puts "INFO: Create HW Platform and BSP"
exec xsdk -batch -eval "sdk set_workspace [file normalize "$proj_dir/$prj_name.sdk"]; sdk create_hw_project -name $hw_platform_name -hwspec [file normalize "$proj_dir/$prj_name.sdk/$topmodule_name.hdf"]; sdk create_bsp_project -name $facq_bsp_name -hwproject $hw_platform_name -proc $facq_proc_name -os standalone; exec xsdk -eclipseargs -application org.eclipse.cdt.managedbuilder.core.headlessbuild -import [file normalize "$origin_dir/sub/acquisition/sdk/$facq_prj_name/"] -data [file normalize "$proj_dir/$prj_name.sdk"] -vmargs -Dorg.eclipse.cdt.core.console=org.eclipse.cdt.core.systemConsole; exit"

puts "INFO: Project is regenerated"

Скрипт регенерации содержит:

  • Название проекта и относительное расположение песочницы (временных файлов).

  • Указание платформы.

  • Указание топового модуля.

  • Настройка симулятора.

  • Подключение к проекту различных наборов файлов - Verilog, TB, IP, .xdc и т.д.

  • Настройки синтеза (с указанием кристалла!).

  • Настройки имплементации (с указанием кристалла!).

Кроме того, в конец этого скрипта я внес перекомпиляцию Block Design при первом запуске, создание HW Platform, BSP для SDK и подключение проекта прошивки для Microblaze (используется блоком поиска).

Новый скрипт восстановления из существующего проекта можно получить через GUI Vivado (File->Write Project Tcl) или через TCL-консоль командой write_project_tcl:

pwd
cd [get_property DIRECTORY [current_project]]
pwd
write_project_tcl -force prj_somz.tcl

Скрипт регенерации песочницы проще всего запустить непосредственно через Vivado. При этом скрипт (а так же block design, IP-ядра и т.д.) подходит только к определенной версии среды, поэтому первым делом следует узнать требуемую версию в заголовке файла:

korogodin@Diod:~/Oryx/src/fpga$ head -n 2 prj_somz.tcl
#
# Vivado (TM) v2015.3 (64-bit)

Как следует из заголовка, необходимо использовать версию 2015.3. Для миграции подойдут и более свежие версии, но миграция - это не для рядового разработчика.

Скрипт можно запустить из консоли операционной системы:

korogodin@Diod:~/Oryx/src/fpga$ /opt/Xilinx/Vivado/2015.3/bin/vivado -source prj_somz.tcl

а можно из консоли Vivado:

cd ~/Oryx/src/fpga
source prj_somz.tcl

Скрипт создает песочницу и файл проекта. Добавляет к проекту внешние исходные файлы. Настраивает правила синтеза и имплементации. Подключается уже собранный Elf-файл для прошивки MicroBlaze (лежит в репозитории в acquisition/bin, скопированный туда руками ранее).

После этого перекомпилируется Block Design. Для проекта прошивки MicroBlaze в песочнице создается somz.sdk, в него добавляется HW Platform и собирается BSP (microblaze_0, standalone). К проекту подключаются исходники прошивки MicroBlaze из сабмодуля acquisition.

Шаг 3. Правим исходные файлы и собираем прошивку

По завершению выполнения скрипта регенирации проекта (prj_somz.tcl в нашем примере) мы имеем готовую к работе настроенную среду:

Можно вносить изменения в исходные файлы и вносить их в коммиты.

Помимо осноного процессор СНК, мы используем ядро небольшого процессора MicroBlaze, размещаемого непосредственно в ПЛИС. Если нужны правки в прошивке MicroBlaze'а, то придется открывать SDK. Для этого в Vivado следует нажать File->Launch SDK. Проект прошивки блока поиска (mcs_facq) изменяется и компилируется в XSDK.

Проект прошивки, BSP, HW Platform подключается автоматически скриптом prj_somz.tcl при регенерации проекта. Для этого в нем используется следующий хак:

exec xsdk -batch -eval "sdk set_workspace [file normalize "$proj_dir/$prj_name.sdk"]; sdk create_hw_project -name $hw_platform_name -hwspec [file normalize "$proj_dir/$prj_name.sdk/$topmodule_name.hdf"]; sdk create_bsp_project -name $facq_bsp_name -hwproject $hw_platform_name -proc $facq_proc_name -os standalone; exec xsdk -eclipseargs -application org.eclipse.cdt.managedbuilder.core.headlessbuild -import [file normalize "$origin_dir/sub/acquisition/sdk/$facq_prj_name/"] -data [file normalize "$proj_dir/$prj_name.sdk"] -vmargs -Dorg.eclipse.cdt.core.console=org.eclipse.cdt.core.systemConsole; exit"

После внесения изменений можно собрать прошивку для ПЛИС:  выбираем число каналов коррелятора, размер ядра блока поиска, тип корреляторов и т.п. и запускам Generate Bitstream слева снизу в Vivado. Процесс сборки пошел!

Если повезет, то через несколько минут/часов/дней мы получим bit-файл, готовый для прошивки в ПЛИС (impl_2 - набор правил имплементации, описан в prj_somz.tcl):

korogodin@Diod:~/Oryx/src/fpga$ find ./ -name *.bit
/home/korogodin/Oryx/src/fpga/prj_somz/somz.runs/impl_2/somz.bit

Заключение

Описан способ хранения исходных кодов проекта для СнК Zynq в системе контроля git. В репозитории размещаются текстовые файлы, создаваемые разработчиком, плюс автоматически формируемых скрипт и xml-описание блок-дизайна. Это позволяет контролировать вносимые изменения при процессе слияния веток и быстрее выявлять источники ошибок при их возникновении.

Проект может быть восстановлен на любом компьютере с подходящей версией Vivado. Прошивка может быть собрана в консольном режиме без применения графического интерфейса и участия человека, что позволяет включить FPGA часть проекта в контур автоматического тестирования.

Комментарии (16)


  1. vassabi
    22.08.2022 13:15
    +1

    полстатьи удивлялся: "почему tcl-скриптов ?"

    Я не против tcl, я на нем сам писал код, но почему не python или не bash ?

    А потом увидел, что у самого IDE есть туда интеграция (на скриншоте есть даже "Tcl Сonsole" внизу), он эти скрипты может исполнять "на своих батарейках" - и вопрос отпал.


    1. alinkagalichina
      22.08.2022 15:31
      +3

      Из плюсов написания скриптов для неродной консоли: очень сильно прокачивается знание документации. Был опыт разработки автоматизации для симулятора QuetsaSim на питоне. Пока раскуривала их tcl команды много новых возможностей тула открыла.


  1. alinkagalichina
    22.08.2022 15:26
    +1

    Статья отличная, спасибо. Очень подробно расписано. Материалов на тему автоматизации для RTL разработки очень не хватает.

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

    Как будто бы не хватает папки с документацией проекта или хотя бы readme. Мы заводили для этого отдельную папочку в структуре гита и держали там все хаки проекта.

    Скажите, используете ли вы в проектах ip core, которые предоставляет Xilinx? Я имею ввиду всякие FIR filter, FFT и прочее. Если да, то где храните их индивидуальные настройки, например входную-выходную ширину слов? Мне не удалось в своё время придумать ничего лучше, как хранит в гите .xci, а при сборке регенерировать из них ядра.


    1. Korogodin Автор
      22.08.2022 16:31

      Немного про то, как собираемость прошивки проверяется в GitlabCI можно почитать тут: https://habr.com/en/post/673254/

      IP ядра сейчас не используем (если только для дебага), т.к. проекты переносятся в итоге на ASIC


  1. alinkagalichina
    22.08.2022 15:27

    Фраза

    Подключается уже собранный Elf-файл для прошивки MicroBlaze (лежит в репозитории в acquisition/bin, скопированный туда руками ранее).


    не очеь бьётся с
    Прошивка может быть собрана в консольном режиме без применения графического интерфейса и участия человека, что позволяет включить FPGA часть проекта в контур автоматического тестирования.


    В моём понимании автоматическое тестирование не должно включать в себя докопирование файлов извне репозитория, тем более руками.

    Мы держали .elf в одном из сабмодулей проекта в гите. Не лучшее решение, но его поддержкой и обновлением занималась другая команда, а написать скрипт для его регенерации не дошли руки.


    1. Korogodin Автор
      22.08.2022 16:20

      Спасибо за замечение! Эльфиник бы копировался скриптом, но он у нас менялся очень редко, а пару лет назад мы вообще отказались от микроблейза. Поэтому к моменту автоматизации тестирования этой проблемы уже не было


  1. te3s
    22.08.2022 15:51

    Что из этого списка хранить в репозитории? Что "рукописного" мы вносим в проект?
    Настройки Xilinx'овских модулей в Block Diagram (.bd)

    Почему вы не храните bd как tcl-скрипт? В этом случае не придется править gitignore, так как output tcl скрипта может быть полностью перенаправлен в локацию вне репозитория (там, где вы храните сгенерированные файлы, то бишь проект).

    Как решатся проблема с IP ядрами? Можно версировать и смотреть diff для xci файлов, но их придется каждый раз пересобирать, что может занимать очень много времени на больших проектах. xcix пересобирать не надо, однако web diff (если у вас code-review система, типа gitlab или gerrit) для xcix не работает, потому что это архив, а не plain text. Как вы решаете эту проблему?


    1. Korogodin Автор
      22.08.2022 16:37

      Tcl-скрипт для bd-файла надо было обновлять руками (нажимать кнопку в интерфейсе File->Export при открытом Block Design в IP Integrator'е). Разработчик легко может забыть это сделать после правки BD файла, дополнительный риск.

      IP ядра не используем. Если самописные модули, то оформляем сабмодулями, как описано выше. Если это ядра от Xilinx, то это нас потом ограничит при переносе проекта на ASIC.


      1. Nahrenako
        23.08.2022 08:29

        Есть много вещей которые после разводки удобнее посмотреть в гуи:

        тайминги, расположение входных/выходных регистров (положил их разводчик в IO как просили или нет), просто на схему имплементации посмотреть и тд


      1. te3s
        24.08.2022 14:53

        IP ядра не используем.

        Жестко)


  1. AlexanderS
    22.08.2022 18:40
    +1

    Казалось бы вот оно решение — использовать non-project подход, отличный задел для автоматизации. Но на практике разработчики его отторгают. Людям привычнее и быстрее работать в GUI.

    В GUI после имплементации, если есть ошибки, можно погенерить и поизучать отчёты и посмотреть визуально, например, пути. В non-project всё менее дружелюбнее, после разводки скриптом анализировать логи надо, чтобы узнать о нарушении времянки. Поэтому тут вопрос не отторжения, а банального удобства.


    1. Andruwkoo
      24.08.2022 02:13
      +2

      При работе в non-project во-первых, в любой момент в процессе сборки (в том числе в случае ошибки) можно открыть GUI командой start_gui и тогда откроется окно аналогичное, проектному режиму. При этом можно на месте исправить некоторые ошибки и продолжить сборку дальше просто вызвав соответствующие команды в консоли. Во-вторых можно сохранять чекпоинты (.dcp файлы) в любой момент в процессе сборки и после открыть их в GUI вивады и анализировать результаты. Притом возможности аналогичные, как если бы работать в проектном режиме.

      ИМХО не проектный режим просто чуть сложнее и надо курить мануалы вивады - поэтому и не популярный, но сильно упрощает работу после в плане автоматизации (в любом месте скрипта сборки можно вставить любое действие)


    1. te3s
      24.08.2022 14:53

      Согласен с предыдущим комментатором. В non-project mode (хотя там есть еще batch mode какой-то, не суть важно) надо сохранять design check point файлы (dcp) после каждого шага. Чтобы узнать о нарушении времянки достаточно просто грепнуть 'Timing Summary' табличку из timing report, это как бы вообще не препятствие.

      Поэтому тут вопрос не отторжения, а банального удобства.

      Это наоборот удобно. То, что заскриптовано в non-project можно сразу же использовать и в project mode. Non-project дает возможность сразу запихать проект в CI/CD, что просто must have на любом (даже небольшом) проекте.


  1. KeisN13
    23.08.2022 12:28

    Эх ностальгия по строму интерфейсу вивадо. Жаль автор из 2022 пишет статью по виваде 2015.3 :) Но уверен, что работать будет. Кудос тебе!


    1. Korogodin Автор
      23.08.2022 12:32
      +1

      Спасибо :)

      Тут скорее автор образца 2022 года репостит себя образца 2016 года по просьбам, поступившим после предыдущей статьи про автоматизирование (ага, вот это "вы часто спрашиваете меня в комментациях", когда на самом деле написал один человек)