Разработка под программируемые логические интегральные схемы (ПЛИС) и систем на кристалле (СНК) отличается монструозностью 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 часть проекта в контур автоматического тестирования.
Автор: Илья Корогодин