Как подружить Vivado и git: с микроблейзом и сабмодулями

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 и т.д. Идея заключается в том, чтобы выкинуть вовне исходные файлы и держать их в системе контроля версий, а в каталоге проекта оставить только генерируемые:

934667637b2c367af19784e9dd50685a.png

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

9e7a2fb6dc1d637d91a1c5d5b18ff9de.png

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

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

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 \]"
  puts "$script_file -tclargs \[--help\]\n"
  puts "Usage:"
  puts "Name                   Description"
  puts "-------------------------------------------------------------------------"
  puts "\[--origin_dir \]  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.

5e0798ca36f10d5059fc6cdf3f9d74bb.png

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

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

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 часть проекта в контур автоматического тестирования.

© Habrahabr.ru