The Jerusalem PostIndian national killed, New Zealander injured, after helicopters collide in Mount Everest regionBollywood HungamaRamayana 3 on the cards? Namit Malhotra says makers may revisit plans after audience feedbackPunchAFC postpones 2027 Asian Cup till DecemberDaily MaverickVIDEO: Poll Dancing: Watch Politically Aweh’s elections comedy special before you voteObservador DesportoPense-se o que se pensar, a acampada teve uma grande vitória3DNewsApple установила рекорд по продажам iPhone, несмотря на отсутствие базового iPhone 18ZDF heuteAktuelle Pressemitteilungen des ZDFWirtualna PolskaPlan obalenia prezydenta przez TK? Myrcha: "Nie widziałem"NOSKamerleden naar Zierikzee voor eerste officiële vergadering buiten Den HaagkickerWattenscheid 09: Aufgeheizte Stimmung vor dem Bochumer DerbySRF NewsKrieg in der Ukraine – Selenski wird laut Merz in Berlin erwartetגיקטייםסלחו על הצרפתית שלי, אבל החוק למיסוי הורדות בצרפת הוא אחד הרעיונות הכי מטומטמים ששמעתי
The Daily Newsstand · Free, Always
Tuesday, November 3, 2026

Как объединить ПЛИС и микроконтроллер в одну JTAG‑цепь: прошивка и совместная отладка

Translate

В команде Автономного транспорта Яндекса мы занимаемся разработкой лидаров. Когда мы были готовы перейти от прототипа к опытному производству, перед нами встала задача организовать приёмочные испытания его компонентов, в том числе проверку качества пайки. Для этого мы решили объединить ПЛИС и микроконтроллер в одну JTAG‑цепь. Оставалось выяснить, как такое объединение повлияет на прошивку и отладку обоих устройств.

Меня зовут Дмитрий, я из команды разработки лидаров. Расскажу, как мы собирали тестовую цепь из STM32 и Xilinx Artix-7, настраивали прошивку через OpenOCD и openFPGALoader и пробовали совместную отладку через Vivado. В статье собрал рабочие конфигурации, ограничения инструментов и решения, которые пришлось искать на форумах и проверять экспериментами.

JTAG — швейцарский нож мира микросхем

Если вы имели дело с микроконтроллерами или ПЛИС, то сталкивались с интерфейсом JTAG. О его особенностях и внутреннем устройстве можно почитать в цикле статей, из которых я и сам черпал информацию в процессе работы.

Нам JTAG был интересен по двум главным причинам:

  1. Для ПЛИС это основное, если не единственное решение для прошивки. А для микроконтроллеров (в частности, для STM32, о которых пойдёт речь дальше) он делит нишу интерфейса прошивки и дебага с проприетарным SerialWire.

  2. Помимо простой передачи информации, у протокола есть функции для управления ведомой микросхемой.

Также свою роль сыграет то, что JTAG реализует довольно необычный для этого уровня интерфейс: ведомые устройства соединяются друг с другом по цепочке «выход ко входу» и последовательно передают данные от ведущего.

Но изначально у нас был вопрос контроля серийных изделий: какое отношение к нему имеет JTAG? Ответ в одной из дополнительных функций — доступе к управлению выводами микросхемы. Представьте: вы вообще не прошиваете микросхему (возможно, её и вовсе нельзя прошить), но при этом можете устанавливать логические 1 и 0 на её функциональные ноги, а также считывать приходящие извне значения.

Это хороший и довольно бюджетный способ выявления дефектов пайки: специальный скрипт подаёт значения на ноги одной микросхемы и читает их с ног другой, проверяя наличие корректных электрических контактов и отсутствие некорректных. А если объединить микросхемы в общую цепь, можно делать это с помощью минимального числа внешних выводов платы.

Чтобы проверить, как прошивка и совместная отладка будут работать в единой JTAG‑цепи, мы собрали тестовый стенд. 

Тестовый стенд: собираем STM32F4 и Xilinx Artix-7 в единую JTAG‑цепь

Для исследования проблемы коллеги раздобыли две отладочные платы — NUCLEO с контроллером STM32F446RE и безымянную плату с чипом Xilinx семейства Artix-7(XC7A200T). Я соединил их в цепь с помощью специализированного высокотехнологичного жгута и подключился программатором Digilent HS-3.

Тестовый стенд

Тестовый стенд

Соединить устройства в цепочку можно двумя способами — в зависимости от порядка сигналов TDI и TDO. Чуть позже это сыграет свою роль:

Конфигурация цепи TDO STM32 в TDI ПЛИС

Конфигурация цепи TDO STM32 в TDI ПЛИС

Конфигурация цепи TDI STM32 в TDO ПЛИС

Конфигурация цепи TDI STM32 в TDO ПЛИС

На этом стенде мы:

  • поработаем с JTAG с помощью UrJTAG;

  • прошьём микроконтроллер и ПЛИС с помощью OpenOCD;

  • прошьём ПЛИС с помощью openFPGALoader;

  • опробуем возможности совместной отладки ПЛИС и микроконтроллера.

UrJTAG: проверка связи 

UrJTAG — это многофункциональная утилита для работы с JTAG. В будущем мы планируем использовать её для управления вводами и выводами микросхем, но пока наша задача — получить первую обратную связь от собранной цепи.

Утилиту нужно собирать из исходников (в папке doc лежит подробная инструкция по установке). После сборки программа доступна по команде jtag — только не забудьте выполнить ldconfig, чтобы динамический линковщик увидел новые библиотеки.

При запуске jtag открывается собственный интерактивный терминал. Сначала связываемся с адаптером с помощью команды cable. В моём случае это выглядит так: 

cable DigilentHS1 vid=0x0403 pid=0x6014

Теперь мы можем вызвать команду detect и увидеть результат:

IR length: 15
Chain length: 3
Device Id: 01001011101000000000010001110111 (0x4BA00477)
  Unknown manufacturer! (01000111011) (/usr/local/share/urjtag/MANUFACTURERS)
Device Id: 00000110010000100001000001000001 (0x06421041)
  Unknown manufacturer! (00000100000) (/usr/local/share/urjtag/MANUFACTURERS)
Device Id: 00010011011000110110000010010011 (0x13636093)
  Manufacturer: Xilinx (0x093)
  Part(2):      xc7a200t (0x3636)
error: Unable to open file '/usr/local/share/urjtag/xilinx/xc7a200t/STEPPINGS'
  Unknown stepping! (0001) (/usr/local/share/urjtag/xilinx/xc7a200t/STEPPINGS)

Видно всё, что нам нужно: в цепи мы обнаружили три устройства, одно из них — ПЛИС от Xilinx, остальные нам неизвестны, поэтому адекватно связаться с ними мы сейчас не можем. Почему устройств оказалось три, а не два — разберём чуть позже. Если передать UrJTAG нужные описания компонентов, дальше можно делать с устройствами всё, что нужно. Но сейчас остановимся на этом и перейдём к прошивке и отладке.

Цепочки OpenOCD и JTAG

OpenOCD — это открытый инструмент для прошивки и отладки микросхем. В основном его используют, чтобы поднять GDB‑сервер при отладке кода на микроконтроллерах, но может он гораздо больше. 

Сначала настроим корректную работу OpenOCD. И пока что буду использовать версию 0.12.0, которая по умолчанию ставится пакетным менеджером Ubuntu 24. Чуть позже это окажется важным.

Подключаем микроконтроллер

Для работы с микроконтроллером OpenOCD поднимает GDB‑сервер, к которому потом подключается отладчик — чтобы загружать бинарники, ходить по шагам и расставлять точки останова.

Попробуем запустить простую команду OpenOCD для конфигурации «TDO STM32 в TDI ПЛИС»:

openocd -f interface/ftdi/digilent_jtag_hs3.cfg -f target/stm32f4x.cfg

В ответ получаем ошибку: 

openocd -f interface/ftdi/digilent_jtag_hs3.cfg -f target/stm32f4x.cfg
Open On-Chip Debugger 0.12.0
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 2000 kHz
Info : JTAG tap: stm32f4x.cpu tap/device found: 0x13636093 (mfg: 0x049 (Xilinx), part: 0x3636, ver: 0x1)
Warn : JTAG tap: stm32f4x.cpu       UNEXPECTED: 0x13636093 (mfg: 0x049 (Xilinx), part: 0x3636, ver: 0x1)
Error: JTAG tap: stm32f4x.cpu  expected 1 of 1: 0x4ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x4)
Info : JTAG tap: stm32f4x.bs tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x4)
Info : JTAG tap: auto0.tap tap/device found: 0x06421041 (mfg: 0x020 (STMicroelectronics), part: 0x6421, ver: 0x0)
Error: Trying to use configured scan chain anyway...
Warn : AUTO auto0.tap - use "jtag newtap auto0 tap -irlen 2 -expected-id 0x06421041"
Error: auto0.tap: IR capture error; saw 0x0002 not 0x0001
Warn : Bypassing JTAG setup events due to errors
Error: Invalid ACK (6) in DAP response

Сущность этого лога в следующем: OpenOCD пошёл сканировать цепь и нашёл Xilinx‑чип с id 0×13636093, которого встретить не ожидал. 

Исправим эту ситуацию. Напишем свой конфигурационный файл для OpenOCD такого вида:

source [find interface/ftdi/digilent_jtag_hs3.cfg]
source [find cpld/xilinx-xc7.cfg]
source [find target/stm32f4x.cfg]

Запускаем OpenOCD с нашим конфигом через флаг ‑f и видим в логе заветные строчки:

Info : starting gdb server for stm32f4x.cpu on 3333
Info : Listening on port 3333 for gdb connections

Порядок, в котором подключаются конфиги Xilinx и STM32, принципиален: если поменять их местами, то устройства в цепи, называемые здесь тапами, будут расположены неправильно, в итоге мы снова получим ошибку о не совпавших идентификаторах. Соответственно, для конфигурации TDI STM32 в TDO ПЛИС эти файлы нужно подключать в обратном порядке.

Теперь, когда GDB‑сервер поднялся, мы можем подключаться к нему с помощью клиента или с помощью IDE. Launch‑конфигурация для отладки при помощи расширения Cortex‑debug для VS Code будет выглядеть примерно так:

"name": "Chain test",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"executable": "build/Debug/JTAGChainTest.elf",
"device": "STM32F446RE",
"configFiles": [
	"stm_tdo_config.cfg"
],
"svdFile": "./thirdparty/lib/stm32/svd/STM32F446.svd",
"runToEntryPoint": "main",
"showDevDebugOutput": "raw",
"liveWatch": true,

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

OpenOCD и ПЛИС

С ПЛИС всё обстоит иначе: основное средство отладки — ILA (Internal Logic Analyzer), с помощью которого можно смотреть временные диаграммы сигналов внутри кристалла. Однако этот инструмент проприетарный, и работать с ним получится только через ПО от производителя (в нашем случае Vivado). Поэтому ограничимся тем, что научимся прошивать ПЛИС с помощью OpenOCD.

Здесь стоит пояснить важный нюанс. Процесс, который я для простоты называю прошивкой ПЛИС, можно проводить по двум сценариям:

  • Прямая конфигурация ячеек ПЛИС. Прошивка сразу начинает работать, но сбрасывается при сбросе питания.

  • Запись во FLASH‑память. Конфигурация сохраняется в энергонезависимой памяти, и ПЛИС сама считывает её оттуда при каждой перезагрузке.

Нам нужно реализовать оба варианта. Начнём с первого — он проще в реализации.

Вся нужная нам информация лежит в конфиге cpld/xilinx-xc7.cfg. Мы уже задействовали его выше, когда настраивали микроконтроллер, поэтому давайте заглянем внутрь. Найти этот файл можно в папке scripts у OpenOCD (например, по пути /usr/share/openocd/scripts).

Конфиг
# SPDX-License-Identifier: GPL-2.0-or-later

# xilinx series 7 (artix, kintex, virtex)
# http://www.xilinx.com/support/documentation/user_guides/ug470_7Series_Config.pdf

if { [info exists CHIPNAME] } {
	set _CHIPNAME $CHIPNAME
} else {
	set _CHIPNAME xc7
}

# the 4 top bits (28:31) are the die stepping/revisions. ignore it.
jtag newtap $_CHIPNAME tap -irlen 6 -ignore-version \
	-expected-id 0x03622093 \
	-expected-id 0x03620093 \
	-expected-id 0x037C4093 \
	-expected-id 0x0362F093 \
	-expected-id 0x037C8093 \
	-expected-id 0x037C7093 \
	-expected-id 0x037C3093 \
	-expected-id 0x0362E093 \
	-expected-id 0x037C2093 \
	-expected-id 0x0362D093 \
	-expected-id 0x0362C093 \
	-expected-id 0x03632093 \
	-expected-id 0x03631093 \
	-expected-id 0x03636093 \
	-expected-id 0x03647093 \
	-expected-id 0x0364C093 \
	-expected-id 0x03651093 \
	-expected-id 0x03747093 \
	-expected-id 0x03656093 \
	-expected-id 0x03752093 \
	-expected-id 0x03751093 \
	-expected-id 0x03671093 \
	-expected-id 0x036B3093 \
	-expected-id 0x036B7093 \
	-expected-id 0x036BB093 \
	-expected-id 0x036BF093 \
	-expected-id 0x03667093 \
	-expected-id 0x03682093 \
	-expected-id 0x03687093 \
	-expected-id 0x03692093 \
	-expected-id 0x03691093 \
	-expected-id 0x03696093 \
	-expected-id 0x036D5093 \
	-expected-id 0x036D9093 \
	-expected-id 0x036DB093

pld device virtex2 $_CHIPNAME.tap 1

set XC7_JSHUTDOWN 0x0d
set XC7_JPROGRAM 0x0b
set XC7_JSTART 0x0c
set XC7_BYPASS 0x3f

proc xc7_program {tap} {
	global XC7_JSHUTDOWN XC7_JPROGRAM XC7_JSTART XC7_BYPASS
	irscan $tap $XC7_JSHUTDOWN
	irscan $tap $XC7_JPROGRAM
	runtest 60000
	#JSTART prevents this from working...
	#irscan $tap $XC7_JSTART
	runtest 2000
	irscan $tap $XC7_BYPASS
	runtest 2000
}

В глаза бросается процедура xc7_program. Не дайте себя обмануть, она ничего не программирует — на это тонко намекает тот факт, что процедура не принимает путь к файлу. Гораздо важнее строчка pld device virtex2 $_CHIPNAME.tap 1 — регистрация программируемого логического устройства на тап ПЛИС. В документации OpenOCD есть и другие pld‑команды, в том числе pld load, которую и будем использовать.

Итак, подготовив bit‑файл для загрузки в ПЛИС, используем команду:

openocd -f stm_tdo_config.cfg -c "adapter speed 6000; init; pld load 0 file.bit; exit"

В случае успеха команда отрабатывает молча, но я верифицирую успешность процесса с помощью Vivado Lab: в Hardware Manager у ПЛИС можно увидеть пометку Programmed. Там же можно заметить, что тапов в цепи не два, а три: ПЛИС, arm‑ядро микроконтроллера и ещё один тап, неизвестный Vivado, — периферия контроллера, которая выполнена отдельным звеном JTAG‑цепочки.

Описание цепочки в Vivado

Описание цепочки в Vivado

Теперь перейдём к вопросу, как писать программу во FLASH. Под капотом у этого процесса две операции: 

  1. Запись на саму ПЛИС специальной прошивки, которая умеет работать с FLASH по SPI. 

  2. Передача этой самой прошивке уже настоящего файла, который она запишет во FLASH.

Работа с FLASH в OpenOCD осуществляется с помощью специальных драйверов. Нас интересует jtagspi‑драйвер. В его описании в документации нет ничего подходящего нам, зато в конфигах OpenOCD есть файл cpld/jtagspi.cfg.

# SPDX-License-Identifier: GPL-2.0-or-later

set _USER1 0x02

if { [info exists JTAGSPI_IR] } {
	set _JTAGSPI_IR $JTAGSPI_IR
} else {
	set _JTAGSPI_IR $_USER1
}

if { [info exists TARGETNAME] } {
	set _TARGETNAME $TARGETNAME
} else {
	set _TARGETNAME $_CHIPNAME.proxy
}

if { [info exists FLASHNAME] } {
	set _FLASHNAME $FLASHNAME
} else {
	set _FLASHNAME $_CHIPNAME.spi
}

target create $_TARGETNAME testee -chain-position $_CHIPNAME.tap
flash bank $_FLASHNAME jtagspi 0 0 0 0 $_TARGETNAME $_JTAGSPI_IR

proc jtagspi_init {chain_id proxy_bit} {
	# load proxy bitstream $proxy_bit and probe spi flash
	global _FLASHNAME
	pld load $chain_id $proxy_bit
	reset halt
	flash probe $_FLASHNAME
}

proc jtagspi_program {bin addr} {
	# write and verify binary file $bin at offset $addr
	global _FLASHNAME
	flash write_image erase $bin $addr
	flash verify_bank $_FLASHNAME $bin $addr
}

В целом всё работает так, как описано выше: 

  • есть процедура jtagspi_init, которая загружает в ПЛИС proxy‑прошивку и регистрирует FLASH. Эту прошивку можно сгенерировать с помощью скрипта contrib/loaders/flash/fpga/xilinx_bscan_spi.py, как написано в документации к драйверу jtagspi, или найти готовый файл в интернете; 

  • есть процедура jtagspi_program, которая затем записывает в этот FLASH прошивку и верифицирует. 

Напишем и запустим вот такой конфиг для случая TDO STM32 в TDI ПЛИС:

reset_config none

source [find interface/ftdi/digilent_jtag_hs3.cfg]
source [find cpld/xilinx-xc7.cfg]
source [find cpld/jtagspi.cfg]
# source [find target/stm32f4x.cfg]

adapter speed 4000
init
jtagspi_init 0 bscan_spi_xc7a200t.bit
jtagspi_program test_jtag.bin 0x0
shutdown

Мы увидим длинный лог о записи данных в разные секторы FLASH и успешное завершение процедуры. Перезагружаем ПЛИС, потому что без специальных действий с нашей стороны прошивка из FLASH подхватывается только после перезагрузки, открываем Vivado и видим... статус Not programmed, который сигнализирует о том, что мы что‑то сделали неправильно.

Начнём диагностику с прошивки FLASH с помощью Vivado. И — о чудо! — ПЛИС свободно загружается и отлично работает. Значит, с файлом всё в порядке, и остаётся понять, что мы делаем не так. Очевидно, Vivado производит с файлом или флешкой какие‑то манипуляции, которых не делает OpenOCD.

Ответ на этот вопрос узнать легко, как нам сначала казалось, — и в Vivado, и в OpenOCD есть функция верификации: программа считывает содержимое FLASH, сравнивает его с заданным файлом и сообщает о любых несовпадениях байтов. В OpenOCD за это отвечает команда flash verify_bank $_FLASHNAME $bin $addr, которая как раз выполняется внутри jtagspi_program.

Прошьём FLASH с помощью Vivado и верифицируем с помощью OpenOCD — верификация успешна. Окей, похоже, что OpenOCD не только не пишет эти участки FLASH, но и не верифицирует их. Но Vivado должна нам показать, что сделал не так OpenOCD. Прошиваем FLASH с помощью OpenOCD и верифицируем при помощи Vivado, и... верификация успешна. Почему при одинаковых результатах проверки ПЛИС загружается только в одном случае, по‑прежнему непонятно. 

Но у нас есть последний рубеж — с помощью Vivado сдампить FLASH после двух прошивок и сравнить полученные файлы. Впечатление, которое на меня произвело абсолютное совпадение этих файлов, сравнимо с тем, что чувствовал Пильман из «Пикника на обочине», когда думал о людях, переживших Посещение: следов воздействия нет, а катастрофы есть. У нас есть два одинаковых файла во FLASH, загруженных из разных источников. С одним из них ПЛИС грузится, с другим — нет.

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

FLASH‑память, помимо самого массива памяти, обычно содержит набор регистров для управления: конфигурация, чтение статуса и так далее. Если заглянуть в даташит на наш FLASH‑чип (S25FL256S в моём случае — его, кстати, умеет опознавать proxy‑прошивка ПЛИС), то там найдётся конфигурационный регистр CR1 с битом QUAD. Он отвечает за включение QUAD‑режима, который необходим для того, чтобы ПЛИС смогла успешно загрузиться из FLASH.

Теперь, когда мы разобрались, что нужно сделать с FLASH, осталось понять — как. Согласно официальной документации OpenOCD, jtagspi‑драйвер умеет выполнять команду jtagspi cmd bank_id resp_num cmd_byte ..., с помощью которой можно посылать и принимать байты с FLASH по SPI. В итоге наш конфиг выглядит примерно так (для верификации добавил ещё и чтение регистра, который мы писали):

reset_config none

source [find interface/ftdi/digilent_jtag_hs3.cfg]
source [find cpld/xilinx-xc7.cfg]
source [find cpld/jtagspi.cfg]
# source [find target/stm32f4x.cfg]

adapter speed 4000
init
jtagspi_init 0 bscan_spi_xc7a200t.bit
jtagspi_program test_jtag.bin 0x0
jtagspi cmd 0 0 0x06
jtagspi cmd 0 0 0x01 0x00 0x02
echo [jtagspi cmd 0 1 0x35]
shutdown

И вот наконец мы запускаем OpenOCD, перезагружаем устройство и видим заветный статус Programmed.

Правда, и здесь есть несколько подводных камней. Начнём с того, что, если вы работаете на Ubuntu® 22, где по умолчанию из пакетного менеджера установлен OpenOCD 0.11, который вы не обновляли, этот скрипт упадёт с ошибкой, так как команда jtagspi ему неизвестна. С OpenOCD 0.12 скрипт работает корректно. Однако здесь нас ждёт ещё одно испытание: если поменять конфигурацию на TDI STM32 в TDO ПЛИС и попытаться прошиться, новый скрипт будет выглядеть так:

reset_config none

source [find interface/ftdi/digilent_jtag_hs3.cfg]
source [find target/stm32f4x.cfg]
source [find cpld/xilinx-xc7.cfg]
source [find cpld/jtagspi.cfg]

adapter speed 4000
init
jtagspi_init 0 bscan_spi_xc7a200t.bit
jtagspi_program test_jtag.bin 0x0
jtagspi cmd 2 0 0x06
jtagspi cmd 2 0 0x01 0x00 0x02
echo [jtagspi cmd 2 1 0x35]
shutdown

Обратите внимание: первый аргумент команды jtagspi cmd изменился на 2. Это индекс нужного банка FLASH. Его изменение связано с тем, что в системе добавились ещё два банка памяти от STM32 (список всех зарегистрированных банков можно посмотреть с помощью команды echo [flash banks]):

#0 : stm32f4x.flash (stm32f2x) at 0x00000000, size 0x00000000, buswidth 0, chipwidth 0
#1 : stm32f4x.otp (stm32f2x) at 0x1fff7800, size 0x00000000, buswidth 0, chipwidth 0
#2 : xc7.spi (jtagspi) at 0x00000000, size 0x02000000, buswidth 0, chipwidth 0

Этот скрипт не работает даже на версии 0.12: регистр не переходит в нужное состояние. Причину этой проблемы мне не удалось выяснить, зато я узнал, что баг уже исправлен в более свежих коммитах в основной ветке, для которых ещё не вышел официальный релиз. 

Я проверял работу на коммите e6752ecbcf72efe4e213e8418e381ff2e0ffdf54 из репозитория. В нём есть подробная инструкция по сборке из исходников, а сообщения об ошибках в установочных скриптах довольно понятные. От себя добавлю только, что digilent_jtag_hs3-драйвер использует ftdi, который не включён в сборку по умолчанию, поэтому при конфигурации не забывайте указать нужный флаг (./configure --enable-ftdi). На этом коммите прошивка во FLASH работает в обеих конфигурациях.

Хотя OpenOCD справился со своей задачей, для прошивки ПЛИС хочется иметь инструмент попроще, который работает без сложных манипуляций с конфигурационными файлами. Посмотрим, насколько удобен в этом плане openFPGALoader.

OpenFPGALoader

OpenFPGALoader — это CLI‑утилита с открытым исходным кодом, которая предназначена для прошивки различных ПЛИС. В отличие от OpenOCD, здесь всё должно запускаться из коробки. Соберём утилиту из исходников и попробуем запустить простенькую команду

openFPGALoader --cable digilent_hs3 --detect

В ней указан кабель, то есть наш JTAG‑адаптер, а также флаг --detect, который позволит посмотреть найденные тапы. Однако выполнение завершится ошибкой: 

JTAG init failed with Unknown device with IDCODE: 0x06421041 (manufacturer: 0x020 (), part: 0x32 vers: 0x0

Это говорит о том, что openFPGALoader не смог разобраться с периферией STM32 и ему нужна наша помощь. Флаг --misc-device <STM_ID_CODE>,<STM_IR_LEN>,stmperiph позволяет указать недостающую информацию о неизвестном тапе. 

В моём случае STM_ID_CODE = 0x06421041, STM_IR_LEN = 5, значения я посмотрел в Vivado, они указаны в описании Unknown‑тапа. В итоге для TDO STM32 в TDI ПЛИС получаем такой результат:

$ openFPGALoader --cable digilent_hs3 --detect --misc-device 0x06421041,5,stmperiph 
Jtag frequency : requested 6.00MHz    -> real 6.00MHz   
index 0:
	idcode   0x6421041
	type     stmperiph
	irlength 5
index 1:
	idcode   0x4ba00477
	type     ARM cortex A9
	irlength 4
index 2:
	idcode 0x3636093
	manufacturer xilinx
	family artix a7 200t
	model  xc7a200
	irlength 6

Теперь попробуем прошить FLASH‑память. Итоговая команда для первой конфигурации TDO STM32 в TDI ПЛИС выглядит так:

openFPGALoader --cable digilent_hs3 --write-flash --enable-quad --fpga-part xc7a200t --verify ~/test_jtag.bin --misc-device 0x06421041,5,stmperiph --index-chain 2

И... она не работает. Не буду держать интригу: заставить утилиту работать при таком порядке устройств в цепи у меня так и не получилось (проверял на коммите 63c519c3c57ded2d8cc412197d8b781115b1922a). 

Чтобы заставить утилиту работать, нужно собрать цепь в конфигурацию TDI STM32 в TDO ПЛИС, индекс ПЛИС в цепи станет 0, и тогда команда openFPGALoader --cable digilent_hs3 --write-flash --enable-quad --fpga-part xc7a200t --verify ~/test_jtag.bin --misc-device 0x06421041,5,stmperiph --index-chain 0 прошьёт FLASH с таким логом:

write to flash
Jtag frequency : requested 6.00MHz    -> real 6.00MHz   
Open file /home/user/test_jtag.bin DONE
Parse file DONE
Use: /usr/local/share/openFPGALoader/spiOverJtag_xc7a200t.bit.gz
load program
Load SRAM: [==================================================] 100.00%
Done
Shift IR 35
ir: 1 isc_done 1 isc_ena 0 init 1 done 1
jtag_chain_len: 3
SOJ version: 2.000000
JEDEC ID: 0x010219
Detected: spansion S25FL256S 512 sectors size: 256Mb
00000000 00000000 00000000 00
start addr: 00000000, end_addr: 00170000
Erasing: [==================================================] 100.00%
Done
Writing: [==================================================] 100.00%
Done
Verifying write (May take time)
Reading: [==================================================] 100.00%
Done
Use: /usr/local/share/openFPGALoader/spiOverJtag_xc7a200t.bit.gz
load program
Load SRAM: [==================================================] 100.00%
Done
Shift IR 35
ir: 1 isc_done 1 isc_ena 0 init 1 done 1
jtag_chain_len: 3
SOJ version: 2.000000
JEDEC ID: 0x010219
Detected: spansion S25FL256S 512 sectors size: 256Mb
set_quad_bit:
Done

Совместная отладка микроконтроллера и ПЛИС

Мы неплохо поработали — научились прошивать и отлаживать микроконтроллер и прошивать ПЛИС. Остался ещё один вопрос. Если микроконтроллер и ПЛИС работают в рамках одного устройства, то с высокой долей вероятности нам потребуется совместно их отлаживать. Как это сделать, если они в единой JTAG‑цепочке?

Объединение устройств в единую цепь накладывает свои условия: мы больше не можем использовать два независимых адаптера, нам нужен единый сервер, который будет общаться со всеми устройствами в цепи, а также осуществлять дистрибуцию сообщений на шине. В теории что‑то подобное может делать OpenOCD, но он не поддерживает инструментарий отладки ПЛИС (в основном речь идёт об ILA — внутреннем логическом анализаторе).

К счастью, помимо ПЛИС, Xilinx производит также SoC, например серию ZYNQ, где в едином устройстве также собраны ПЛИС и микроконтроллер, и, соответственно, предоставляет инструменты для отладки подобных устройств. И Vivado, которую мы уже неоднократно использовали, как раз такой инструмент: когда помимо ПЛИС она встречает в цепи ARM‑ядро, она подключается к нему и поднимает GDB‑сервер, что нам и нужно.

Проверяем отладку через CLI

Запустим Vivado и проверим работу сервера вручную. В консоли запускаем gdb-multiarch и подключаемся к серверу с помощью target extended-remote localhost:3000. 

Моргающий светодиод из тестовой прошивки, оставшейся на плате от последних экспериментов, замирает в одном из положений. Вводим в терминал continue, после чего светодиод снова моргает. 

В ходе этого процесса мы выясним, что группа команд monitor, с помощью которой, например, делается перезагрузка контроллера в случае обычной отладки, урезана. Команда run сообщает, что она не может убить процесс, чтобы перезагрузиться. Однако можно ставить точки останова и ходить по шагам, и если бы мы подтянули elf‑файл прошивки, то могли бы всё это посмотреть.

Настройка отладки в VS Code

Перейдём к более привычному мне формату — к отладке в VS Code. В целом нужно просто повторить то, что мы уже сделали, но с помощью расширения на C++. Напишем вот такой таргет в launch‑файл:

{
    "name": "Remote gdbserver :3000 (multiarch-gdb)",
    "type": "cppdbg",
    "request": "launch",
    "program": "JTAG_test.elf", // Path to elf
    "cwd": "${workspaceFolder}",
    "MIMode": "gdb",
    "miDebuggerPath": "/usr/bin/gdb-multiarch",
    "miDebuggerServerAddress": "localhost:3000",
    "stopAtEntry": false,
}

Запускаем отладку на уже прошитую STM и подключаемся к исполняющейся программе, где мы можем ходить по шагам, ставить точки останова и в целом пользоваться обычным набором инструментов, кроме перезагрузки чипа и его прошивки, — способа сделать это с помощью данного сервера я не нашёл. Предполагаю, это связано с тем, что сервер видит только ARM‑ядро, а о существовании FLASH и всего остального он просто не знает.

Заключение

В этой статье я постарался обобщить опыт, полученный в ходе исследования нестандартной JTAG‑цепочки, и собрать в одно руководство то, что обычно приходится искать по разрозненным даташитам и коммитам.

Краткое резюме всей этой истории:

  • OpenOCD достаточно спокойно чувствует себя в цепочке и свой функционал выполняет свободно.

  • OpenFPGALoader может подкинуть сюрпризов, однако и его удалось заставить работать в каком‑то варианте.

  • Vivado работает без проблем и даже открывает новые возможности — отладку микроконтроллеров.

  • Объединение ПЛИС и микроконтроллера в общую цепочку практически не ущемляет наши возможности прошивки и отладки.

Надеюсь, эта информация и готовые конфигурационные файлы сэкономят вам время при разработке и прошивке собственных устройств.

Если вы сталкивались с подобными JTAG‑цепями, решали другими способами вставшие перед нами проблемы или используете альтернативный софт для тех же целей — поделитесь опытом в комментариях!

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.