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


В команде Автономного транспорта Яндекса мы занимаемся разработкой лидаров. Когда мы были готовы перейти от прототипа к опытному производству, перед нами встала задача организовать приёмочные испытания его компонентов, в том числе проверку качества пайки. Для этого мы решили объединить ПЛИС и микроконтроллер в одну JTAG‑цепь. Оставалось выяснить, как такое объединение повлияет на прошивку и отладку обоих устройств.
Меня зовут Дмитрий, я из команды разработки лидаров. Расскажу, как мы собирали тестовую цепь из STM32 и Xilinx Artix-7, настраивали прошивку через OpenOCD и openFPGALoader и пробовали совместную отладку через Vivado. В статье собрал рабочие конфигурации, ограничения инструментов и решения, которые пришлось искать на форумах и проверять экспериментами.
JTAG — швейцарский нож мира микросхем
Если вы имели дело с микроконтроллерами или ПЛИС, то сталкивались с интерфейсом JTAG. О его особенностях и внутреннем устройстве можно почитать в цикле статей, из которых я и сам черпал информацию в процессе работы.
Нам JTAG был интересен по двум главным причинам:
Для ПЛИС это основное, если не единственное решение для прошивки. А для микроконтроллеров (в частности, для STM32, о которых пойдёт речь дальше) он делит нишу интерфейса прошивки и дебага с проприетарным SerialWire.
Помимо простой передачи информации, у протокола есть функции для управления ведомой микросхемой.
Также свою роль сыграет то, что JTAG реализует довольно необычный для этого уровня интерфейс: ведомые устройства соединяются друг с другом по цепочке «выход ко входу» и последовательно передают данные от ведущего.
Но изначально у нас был вопрос контроля серийных изделий: какое отношение к нему имеет JTAG? Ответ в одной из дополнительных функций — доступе к управлению выводами микросхемы. Представьте: вы вообще не прошиваете микросхему (возможно, её и вовсе нельзя прошить), но при этом можете устанавливать логические 1 и 0 на её функциональные ноги, а также считывать приходящие извне значения.
Это хороший и довольно бюджетный способ выявления дефектов пайки: специальный скрипт подаёт значения на ноги одной микросхемы и читает их с ног другой, проверяя наличие корректных электрических контактов и отсутствие некорректных. А если объединить микросхемы в общую цепь, можно делать это с помощью минимального числа внешних выводов платы.
Чтобы проверить, как прошивка и совместная отладка будут работать в единой JTAG‑цепи, мы собрали тестовый стенд.
Тестовый стенд: собираем STM32F4 и Xilinx Artix-7 в единую JTAG‑цепь
Для исследования проблемы коллеги раздобыли две отладочные платы — NUCLEO с контроллером STM32F446RE и безымянную плату с чипом Xilinx семейства Artix-7(XC7A200T). Я соединил их в цепь с помощью специализированного высокотехнологичного жгута и подключился программатором Digilent HS-3.

Соединить устройства в цепочку можно двумя способами — в зависимости от порядка сигналов TDI и 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‑цепочки.

Теперь перейдём к вопросу, как писать программу во FLASH. Под капотом у этого процесса две операции:
Запись на саму ПЛИС специальной прошивки, которая умеет работать с FLASH по SPI.
Передача этой самой прошивке уже настоящего файла, который она запишет во 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‑цепями, решали другими способами вставшие перед нами проблемы или используете альтернативный софт для тех же целей — поделитесь опытом в комментариях!
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.