Pull to refresh

Разбираем протокол 2-wire JTAG

Reading time 10 min
Views 32K


2-wire JTAG (он же двухпроводной JTAG, он же CompactJTAG, он же cJTAG) – это новомодный интерфейс, являющийся частью стандарта IEEE 1149.7-2009. Он обеспечивает ту же и даже большую функциональность, что и обычный JTAG (IEEE 1149.1), но использует всего два сигнала вместо четырех.

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

Я предполагаю, что вы знакомы с обычным JTAG-ом. Освежить память можно здесь: habrahabr.ru/post/190012

Часто считают, что IEEE 1149.7 и 2-wire JTAG – это синонимы. На самом деле это не так, потому что IEEE 1149.7 включает в себя режимы, использующие как два, так и четыре сигнала. Так что 2-wire JTAG – это подмножество IEEE 1149.7.

Официально IEEE 1149.7 называется IEEE Standard for Reduced-Pin and Enhanced-Functionality. Несмотря на то, что помимо двухпроводности IEEE 1149.7 предлагает еще стопицот новых фич, по моему опыту именно возможность сэкономить две ножки на корпусе микросхемы привлекает разработчиков больше всего.

К сожалению, создатели IEEE 1149.7 употребляли тяжелые наркотики безо всякой меры, поэтому родили без преуменьшения чудовищный документ, который твердо занял первое место в моем личном хит-параде самых уродски написанных стандартов. Помимо того, что он вырос почти в десять раз (1037 страниц против 139 у старого JTAG-а), он еще и написан так, что абсолютно невозможно ничего понять.

Тем не менее, если вы однажды окажетесь лицом к лицу с микросхемой, у которой вместо четырех JTAG-ног (TCK, TMS, TDI и TDO) всего две (TCKC и TMSC – “C” на конце означает “Compact”), а готового софта для работы с ней не будет, то вам придется что-то делать.

Что обычно делает инженер, когда видит перед собой микросхему с неопознанным JTAG-портом. Конечно же пытается выяснить, какие устройства к нему подключены. Узнать это можно, прочитав их IDCODE. Для обычного JTAG процедура проста и выглядит так, как описано по ссылке www.fpga4fun.com/JTAG3.html:
  1. Сначала определяем число устройств в цепочке:
    • Переходим в Test Mode Reset
    • Переходим в Shift-IR
    • Задвигаем через TDI кучу единиц (сколько не жалко, например, 1000) во все IR-ы в цепочке. Теперь все устройства в BYPASS
    • Переходим из Shift-IR в Shift-DR
    • Задвигаем через TDI кучу нулей (сколько не жалко, например, 1000) во все DR-ы в цепочке, чтобы обнулить их
    • Начинаем задвигать через TDI единицы в DR. Как только получили единицу из TDO, прекращаем. Число устройств в цепочке равно числу задвинутых единиц.
  2. Потом для каждого устройства читаем IDCODE:
    • Переходим в Test Mode Reset – в результате во все IR-ы записывается адрес регистра IDCODE.
    • Идем в Shift-DR
    • Читаем из TDO 32 бита для каждого устройства (кстати, для этого придется задвинуть что-нибудь в TDI). Первые 32 бита, прочитанные из TDO – IDCODE последнего устройства в цепочке, следующие 32 бита – IDCODE предпоследнего устройства, и т.д.

Изначально я хотел показать, как сделать то же самое, используя двухпроводной JTAG, но оказалось, что эта процедура длинновата для примера. Поэтому я решил упростить ее, предположив, что в JTAG-цепочке только одно устройство, и адрес его регистра IDCODE известен. Таким образом, выглядеть чтение IDCODE будет так:
  1. Записываем в Instruction Register (IR) адрес IDCODE:
    • Идем в Shift-IR
    • Находясь в Shift-IR, задвигаем в TDI адрес регистра IDCODE
  2. Читаем данные из IDCODE:
    • Идем в Shift-DR
    • Находясь в Shift-DR, задвигаем в TDI 32 произвольных бита и одновременно получаем содержимое IDCODE через TDO

Очевидно, что умея выполнять упрощенную процедуру, можно без проблем реализовать и полную, потому что они используют одни и те же команды. Однако без TDI и TDO, которых у 2-wire JTAG нет, ничего из вышеперечисленного работать не будет. Как же предлагается обойтись без них? Очень просто: по TMSC будут передаваться Scan-пакеты, содержащие TMS, TDI и TDO. Вот так выглядят два идущих друг за другом пакета (nTDI означает, что передается инвертированный TDI):


На самом деле, это только один из возможных форматов, описанных в стандарте – он называется OScan1. Есть еще JScan, MScan, SScan, а OScan-ов аж восемь штук. Правда, обязательны для 2-wire JTAG только JScan0-3, MScan, Oscan0 и Oscan1.

Тут нужно заметить, что сигнал TMSC, в отличие от обычного TMS, двунаправленный, потому что в каждом пакете TMS и TDI передаются в одну сторону, а TDO – в другую.

Однако если вы попробуете просто начать передавать OScan-пакеты, то вас ждет неприятный сюрприз. Короче говоря, ничего работать не будет. А все потому, что по умолчанию любое устройство с поддержкой IEEE 1149.7 находится в режиме совместимости со стандартом 1149.1 (так называемом «стандартном режиме» – standard mode) и никакие такие пакеты не воспринимает. Поэтому прежде чем слать всякие OSсan-ы, необходимо такое устройство сконфигурировать для работы в нужном формате и перевести его в «продвинутый режим» (advanced mode).

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


Вместо того, чтобы требовать от разработчиков микросхем переделать всю JTAG-логику, стандарт предлагает добавить адаптер, преобразующий двухпроводной интерфейс в обычный четырехпроводной:


Этот адаптер содержит свой собственный Test Access Port (TAP), называемый TAP.7, чтобы можно было отличить его от обычного TAP (он же TAP.1). У контроллера TAP.7 внутри точно такой же автомат, как у TAP.1, но есть и некоторые отличия, например, у него нет Instruction Register, зато есть много других регистров, которых нет у TAP.1.

По умолчанию TAP.7 «прозрачен», т.е. сигнал TCKC просто напрямую подключен к TCK, а TMSC – к TMS. Поэтому все сигналы, посылаемые по TCKC и TMSC передаются безо всяких изменений на TCK и TMS:


Очевидно, что при попытке передать Oscan1-пакет в таком режиме «nTDI» будет воспринят контроллером TAP.1 просто как первый бит, переданный по линии TMS, «TMS» — как второй бит, и т.д. Именно поэтому перед тем, как передавать пакеты, нужно перевести контроллер TAP.7 в «продвинутый режим». Это делается путем записи в один из внутренних регистров TAP.7. На время конфигурирования TCK и TMS отключаются (а TDI и TDO и раньше были отключены):


После того, как конфигурирование закончено, TCK, TMS, TDI и TDO управляются внутренней логикой контроллера:


Таким образом, процесс переключения в «продвинутый режим» и последующее чтение IDCODE выглядит так:
  1. Инициализировать контроллеры TAP.7 и TAP.1
  2. Отключить TCK и TMS
  3. Выбрать нужный формат передачи
  4. Если выбран формат OScan, MScan или SScan, то перейти в «продвинутый режим»
  5. Подключить TCK, TMS, TDI и TDO
  6. Записать в Instruction Register адрес IDCODE
  7. Прочитать данные из IDCODE

Инициализация TAP.7. Escape Reset

Поскольку никогда нельзя знать, в каком состоянии находятся автоматы TAP.7 и TAP.1, то перед началом работы необходимо сбросить их. Для сброса контроллера TAP.7 применяется Escape Reset – один из видов эскейп-последовательностей, определенных стандартом. От всех остальных JTAG-команд эскейп-последовательности отличаются тем, что значения TMS считываются не по переднему фронту TCK, а асинхронно. Контроллер TAP.7 считает количество фронтов сигнала TMS, пришедших за то время, пока TCKC неизменен. Количество фронтов определяет вид эскейп-последовательности. Для Escape Reset оно должно быть больше семи:


Также Escape Reset переводит TAP.7 в состояние Test Logic Reset. Вся прелесть эскейп-последовательностей в том, что они абсолютно никак не влияют на TAP.1, который их просто игнорирует. TAP.1 считывает значения TMS только по переднему фронту TCK, поэтому ему абсолютно все равно, что происходит с TMS в остальное время.
Вот так на экране осциллографа выглядит Escape Reset, который передает USB-JTAG адаптер Digilent HS2:


Как видно, все восемь фронтов на месте, однако расстояние между шестым и седьмым выглядит подозрительно. Я уже пожаловался в Digilent – обещали исправить.

Инициализация TAP.1

Чтобы сбросить TAP.1 вслед за TAP.7, стандарт рекомендует послать пять единиц по TMSC: они гарантированно переводят TAP-контроллер в Test-Logic-Reset из абсолютно любого другого состояния (можете проверить сами):


Поскольку больше в Test-Logic-Reset возвращаться мы не планируем, то имеет смысл вслед за пятью единицами послать нолик и тем самым перевести автомат в состояние Run-Test/Idle. Напомню, что контроллер считывает значения TMS в момент переднего фронта TCK и сразу же изменяет состояние автомата в соответствии с этими значениями:


Отключение TCK и TMS

Чтобы отключить TCM и TMS, нужно перевести контроллер TAP.7 в режим Control Mode 2. Всего контроллер поддерживает восемь разных Control Mode-ов, пронумерованных от нуля до семи. Нас интересует именно Control Mode 2, так как только в нем появляется доступ к служебным регистрам контроллера, один из которых нам и нужен, чтобы изменить формат пакетов на OScan1.

Переключение между Control Mode-ами осуществляется последовательно путем выполнения так называемого Zero-bit DR Shift (ZBS). Каждый выполненный ZBS увеличивает Control Mode на единицу. В отличие от эскейп-последовательностей, в теории ZBS может повлиять на контроллер TAP.1, однако на практике это крайне маловероятно. С точки зрения стандарта IEEE 1149.1 Zero-bit DR Shift не имеет абсолютно никакого смысла, поэтому разработчики понадеялись, что ни «железо», ни «софт», разработанные до появления 2-wire JTAG, не должны были использовать ZBS ни для каких своих нужд. На временной диаграмме ZBS выглядит так:


Как видно, ZBS начинается и заканчивается в состоянии Run-Test/Idle, поэтому важно не забыть после Escape Reset и Test-Logic-Reset перейти в Run-Test/Idle до того, как посылать первый ZBS.

Когда нужный режим выбран, его нужно зафиксировать (LOCK), после чего последующие ZBS-ы уже не смогут влиять на выбор режима. LOCK – это почти то же самое, что ZBS, только с заходом в Shift-DR:


Заметьте, что состояние TAP.1, в отличие от TAP.7, уже не меняется. Это потому, что TCK и TMS отключаются сразу, как только номер Control Mode-а становится равен двум, даже если он еще не зафиксирован.
Таким образом, чтобы попасть в Control Mode 2, нужно:
  1. Послать два ZBS
  2. Послать LOCK

Выбор формата

Теперь, когда мы находимся в режиме Control Mode 2, можно писать в служебные регистры. Стандарт описывает множество регистров, запись в которые производится специальными командами. Команды делятся на две группы – двухсекционные (two-part command) и трехсекционные (three-part command), которые я рассматривать не буду.

У двухсекционных команд в первой секции передается номер команды, а во второй – данные. Обе секции передаются одинаковым образом: из состояния Run-Test/Idle надо перейти в Shift-DR, после чего вернуться в Run-Test/Idle. Передаваемое значение равно количеству тактов, проведенных в Shift-DR.
Нас интересует команда под номером три — STFMT (Store Scan Format), которая записывает номер желаемого формата (для OScan1 он равен девяти), передаваемый во второй секции, в регистр SCNFMT (Scan Format).

Таким образом, в первой секции нам надо передать тройку, а во второй – девятку. Сначала записываем номер команды, для чего ждем три такта в состоянии Shift-DR (состояние TAP.1 не показано, так как оно не меняется):


Потом записываем данные (девять тактов в состоянии Shift-DR):


Запись в регистр SCNFMT сама по себе формат не меняет. Чтобы контроллер переключил формат, нужно послать специальный проверочный пакет (check packet).

Check Packet

Проверочный пакет проверяет корректность настроек, и если все в порядке, то контроллер переключает формат и, при необходимости, переходит в «продвинутый режим». Как у всего остального в IEEE 1149.7, у проверочного пакета куча разных вариантов. Вам придется поверить мне на слово, что нам нужен пакет с END-директивой (и не спрашивайте, что это значит), который выглядит вот так:


Ура, теперь мы в «продвинутом режиме» и можем наконец-то использовать OScan1! Чтобы вернутся в «стандартный режим», достаточно перейти в Test-Logic-Reset.

Подключение TCK, TMS, TDI и TDO

Итак, мы в «продвинутом режиме», но все еще в Control Mode 2, а это значит, что TAP.1 до сих пор отключен, и прочитать из него IDCODE мы не можем. Стандарт описывает четыре способа выхода из Control Mode:
  1. Перейти в Test-Logic-Reset
  2. Перейти в Select-IR-Scan
  3. Записать единичку в регистр ECL (Exit Control Level), используя двухсекционную команду STMC
  4. Сделать что-то неведомое, связанное с синхронизацией TAP.7-контроллеров

В Test-Logic-Reset нам нельзя, а то контроллер вернется в «стандартный режим». Двухсекционную команду слать долго. А вот пойти в Select-IR-Scan и вернуться в Run-Test/Idle, не проходя через Test-Logic-Reset, довольно просто – нужно всего лишь послать последовательность 011011 по TMS (заходить в Shift-IR не обязательно). Помните, что в JTAG всегда первым передается младший бит:


Красными точками отмечены места, в которых контроллер считывает биты TMS со входа TMSC. По TDI передаются нули (т.е. во всех пакетах биты nTDI равны единице), хотя они все равно игнорируются, так как мы не заходим в состояние Shift-IR.

Кстати, в отличие от «стандартного режима», состояние TAP.7 меняется не сразу же после получения нового бита TMS, а по третьему (последнему) переднему фронту TCKC в OScan1-пакете (на рисунке эти моменты показаны черными стрелочками) – это сделано для того, чтобы контроллер успел вставить в OScan1-пакет бит TDO и отправить его на хост (в этом конкретном случае контроллер возвращает нули по TDO).

Чтение IDCODE

Теперь, когда TAP.1 снова подключен к TAP.7, мы можем наконец прочитать содержимое регистра IDCODE.

Адрес регистра IDCODE не описан в стандарте, поэтому во всех контроллерах он разный (в моем случае он четырехбитный и равен 0xC). Чтобы упростить жизнь пользователям, Instruction Register инициализируется адресом IDCODE в состоянии Test-Logic-Reset. Однако в состоянии Capture-IR содержимое Instruction Register перезаписывается числом 0x1 (вне зависимости от длины Instruction Register его младший бит устанавливается в единицу, а все остальные в ноль) для того, чтобы можно было обнаружить обрыв цепи при тестировании. Так как мы заходили в Capture-IR на предыдущем этапе, то адрес IDCODE уже затерт, поэтому сначала нужно перезаписать его.

Для записи адреса регистра надо войти в состояние Shift-IR и задвинуть адрес (0xC) по TDI младшим битом вперед. При этом на TDO появится значение, находившееся в Instruction Register до этого, т.е. 0x1 (опять же, младшим битом вперед). В случае обычного четырехпроводного JTAG IEEE 1149.1 это выглядело бы так:


А вот так это выглядит при использовании пакетов OScan1:


Красными точками отмечены места, в которых контроллер считывает биты TMS, а зелеными – биты nTDI со входа TMSC. Фиолетовыми точками отмечены места, в которых контроллер считывает биты TDO перед тем, как вставить их в OScan1-пакеты.
Стрелочками показаны некоторые зависимости, например, сигналы TMS и TDI меняются по соответствующим фронтам TCKC, а сигнал TDO меняется по заднему фронту TCK.

Теперь остается только пойти в состояние Shift-DR, задвинуть 32 произвольных бита по TDI и прочесть 32 бита с TDO, которые и будут содержать IDCODE. С вашего позволения временную диаграмму чтения IDCODE приводить не буду.

Заключение

Я описал примерно 5% возможностей IEEE 1149.7. Тем не менее, я надеюсь, что у вас появилось какое-никакое понимание принципов, лежащих в его основе.

Все диаграммы в этой статье проверены на реальном железе с использованием USB-JTAG адаптера Digilent HS2, коммерческого контроллера 2-wire JTAG, к исходным кодам которого у меня есть доступ, отладочной платы с FPGA и логического анализатора ChipScope от Xilinx.

Программа для генерации последовательностей была написана с использованием API для HS2 на основе примера, который можно скачать вместе с Digilent Adept SDK и отлично работала как под Linux, так и под Windows.
Tags:
Hubs:
+50
Comments 4
Comments Comments 4

Articles