![](https://habrastorage.org/getpro/habr/upload_files/28e/f1b/274/28ef1b274347b991b8f2683f08604606.png)
Разработка под программируемые логические интегральные схемы (ПЛИС) и систем на кристалле (СНК) отличается монструозностью 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 и т.д. Идея заключается в том, чтобы выкинуть вовне исходные файлы и держать их в системе контроля версий, а в каталоге проекта оставить только генерируемые:
![](https://habrastorage.org/getpro/habr/upload_files/934/667/637/934667637b2c367af19784e9dd50685a.png)
При выделении исходных файлов в отдельные каталоги конечная структура определяется разработчиком исходя из собственных предпочтений. В репозиторий кладутся только исходные файлы и tcl-скрипт, который позволяет воссоздать каталоги песочницы с генерируемыми файлами. Например:
![](https://habrastorage.org/getpro/habr/upload_files/9e7/a2f/b6d/9e7a2fb6dc1d637d91a1c5d5b18ff9de.png)
Те логические модули, что используются в разных проектах (прошивках разных устройств), вынесены в отдельные сабмодули и подключаются наподобии библиотек. Они при этом являются самостоятельными Vivado-проектами и могут разрабатываться (в том числе тестироваться) независимо.
Блок-дизайны для различных плат вынесены в отдельную директорию bd. Там хранятся непосредственно .bd-файлы, которые являются текстовыми xml-файлами. Вспомогательные бинарные файлы генерируются из них уже вивадой.
![](https://habrastorage.org/getpro/habr/upload_files/ccb/651/be2/ccb651be251e0e6d145c5046b8a47c8b.png)
К сожалению, вспомогательные файлы блок-дизайна генерируются в каталоге с 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.
![](https://habrastorage.org/getpro/habr/upload_files/5e0/798/ca3/5e0798ca36f10d5059fc6cdf3f9d74bb.png)
Шаг 3. Правим исходные файлы и собираем прошивку
По завершению выполнения скрипта регенирации проекта (prj_somz.tcl
в нашем примере) мы имеем готовую к работе настроенную среду:
![](https://habrastorage.org/getpro/habr/upload_files/28e/f1b/274/28ef1b274347b991b8f2683f08604606.png)
Можно вносить изменения в исходные файлы и вносить их в коммиты.
Помимо осноного процессор СНК, мы используем ядро небольшого процессора 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 часть проекта в контур автоматического тестирования.
Update
Реализация Makefile’ов и tcl-скриптов от хабропользователя Владислава: https://github.com/vborchsh/make-fpga