Разглядывая JTAG: идентификация Разглядывая JTAG: *.bsdl своими руками Разглядывая JTAG: что внутри? Ознакомившись с работой JTAG в общих чертах и написав файл BSDL для воображаемой микросхемы в предыдущей статье, можно рассмотреть работу модуля JTAG внутри микросхем более детально. Для этого мы напишем прошивку для микроконтроллера и для ПЛИС (на «Си» и на «SystemVerilog»), которые позволят считывать/устанавливать логические уровни на отдельных выводах микросхемы через данный интерфейс. Прежде чем продолжать разговор непосредственно об интерфейсе JTAG, обратим внимание на особенности работы базового элемента JTAG — сдвигового регистра — вне контекста самого интерфейса. Представим, что у нас имеется сдвиговый регистр, предназначенный для последовательного приёма данных на вход и параллельной выдачи этих данных во вне. Пускай он состоит из 4-х отдельных триггеров (битов), и изначально в регистре находится значение «1100». Если нам необходимо, чтобы регистр выдал значение «0101», то мы будем должны «вдвинуть» эту последовательность в регистр. На это уйдёт 4 такта. Если при этом значения из вне снимаются непосредственно с выходов триггеров сдвигового регистра, то в течение этих четырёх тактов они будут меняться следующим образом: 1100→1110→0111→1011→0101. Подобное, квази-произвольное изменение значений на выходе может быть не только неудобно, но, в ряде случаев даже катастрофично. Поэтому в микросхемах сдвиговых регистров имеется не один, а два регистра (секции). При тактировании первой секции происходит сдвиг данных, а когда все необходимые данные сдвинуты в сдвиговую (последовательную) секцию, то при помощи дополнительного вывода возможно осуществить копирование (обновление) данных из последовательной секции в параллельную секцию. В результате, данные на выходе поменяются с «1100» на «0101» за время порядка длительности фронта тактирования. В случае параллельного входа и последовательного выхода, в регистре достаточно одной лишь последовательной секции. Тем не менее операция сдвига и операция внесения (захвата) данных также должны быть разделены. Если предполагается, что сдвиговый регистр должен работать как в режиме «параллельный вход, последовательный выход», так и наоборот — «последовательный вход, параллельный выход», то в работе с таким регистром будет три характерные режима: захват («Capture») данных в последовательную секцию, сдвиг («Shift») последовательной секции и обновление («Update») данных в параллельной секции. Как видно на схеме конечного автомата JTAG, все три данные состояния имеются в наличие. Сдвиг регистра в модуле JTAG происходит по восходящему фронту TCK в состоянии «SHIFT XX». Загрузка данных в сдвиговый регистр также происходит по восходящему фронту TCK, но в состоянии «CAPTURE XX». Здесь есть тонкий момент. Согласно схеме конечного автомата JTAG, в состоянии «CAPTURE XX» невозможно задержаться дольше одного такта. Причём соответствующий бит, сигнализирующий о необходимости перехода, устанавливается на линии TMS на нисходящем фронте, а вход в это состояние (как и в любое другое) происходит на ближайшем восходящем фронте. Таким образом в непосредственной окрестности от состояния «CAPTURE XX» имеется два восходящих фронта — в начале и в конце данного состояния, если смотреть на временную диаграмму. Так вот срабатывание «CAPTURE XX» происходит по второму восходящему фронту. Если в качестве сдвигового регистра подключен «DEVICE_ID», то в состоянии «CAPTURE DR» по второму восходящему фронту TCK в него будет скопирован идентификационный номер микросхемы. Если подключен «BYPASS», то в том же состоянии по второму восходящему фронту TCK в его единственный бит будет помещён ноль. То же самое произойдёт с регистром инструкций в состоянии «CAPTURE IR» — в него по второму восходящему фронту будет загружено значение, которое указывают в атрибуте «INSTRUCTION_CAPTURE» файла BSDL. В отличие от «CAPTURE XX», «UPDATE XX» выполняется на нисходящем фронте. Это может вызвать вопрос — зачем так было сделано? Почему не сделать единообразно? Ведь гонку фронтов здесь, на первый взгляд, никак не устроить ввиду того, что у всех микросхем цепи JTAG общая линия TMS, а следовательно все микросхемы всегда находятся в одном и том же состоянии. Причина — предусмотренная стандартом, весьма оригинальная возможность соединения микросхем в цепочку JTAG с общей для всех микросхем линией TCK, но с индивидуальными линиями TMS! Технически, подобное соединение способно несколько ускорить процесс тестирования соединений на плате между микросхемами (собственно, для этого JTAG и делался) благодаря тому, что на нисходящем фронте одни микросхемы выдадут тестовые значения на своих выводах, а через половину периода TCK другие микросхемы на восходящем фронте считают (или, если какие-либо линии разорваны, не считают) это значения на своих входах. Мы не будем рассматривать особенности работы с подобной экзотической цепочкой JTAG, но отметим для себя причину, по которой выполнение «UPDATE XX» и «CAPTURE XX» происходит на разных фронтах. Поговорим теперь о подключаемом регистре «BOUNDARY» и инструкциях, связанных с ним. Наиболее простой инструкцией является «SAMPLE». Если была выбрана эта инструкция, то в состоянии «CAPTURE DR» на восходящем фронте TCK в соответствующие биты подключенного регистра «BOUNDARY» будет записано состояние всех доступных для контроля входов микросхемы. Перейдя затем в состояние «SHIFT DR», мы сможем последовательно сдвинуть все биты регистра «BOUNDARY» через TDO и распознать, какие логические значения были на выводах микросхемы. Инструкция «EXTEST» способна как считывать состояния на входах микросхемы, так и устанавливать их на выходах. Здесь есть тонкий момент. Предположим, была выбрана инструкция «EXTEST». Если мы поместим через состояние «SHIFT DR» в последовательную секцию регистра «BOUNDARY» некоторые значения, то они не появятся тут же автоматически на выходах микросхемы, так как в параллельной секции регистра «BOUNDARY» находятся предыдущие значения. Для обновления параллельной секции ранее введёнными данными, мы должны перевести конечный автомат в состояние «UPDATE DR». Логические уровни на выводах микросхемы поменяются в момент прохождения нисходящего фронта в состоянии «UPDATE DR». Однако, значения выходов микросхемы будут поставлены в зависимость от битов регистра «BOUNDARY» как только в регистр инструкций попадёт код инструкции «EXTEST». Произойдёт это в момент прохождения нисходящего фронта в состоянии «UPDATE IR». То есть до этого момента мы ещё ничего не сбрасывали в параллельную секцию регистра «BOUNDARY», а сразу после — данные из неё уже используются. Эти данные могут быть весьма произвольны! Чтобы не допустить попадания на выходы микросхемы произвольных значений, неплохо было бы загрузить в параллельную секцию заранее известные данные, но так, чтобы при этом эта секция была отключена от выходов. Для этой цели нужна инструкция «PRELOAD». Так как инструкция «SAMPLE» предусматривает сброс данных в последовательную секцию регистра «BOUNDARY», а затем их выдвижение через TDO, а «PRELOAD» — задвигание данных в последовательную секцию через TDI, а затем сброс в параллельную секцию (к тому же «выдвижение» и «задвигание» это, по сути, один и тот же процесс сдвига), то эти две инструкции можно объединить в одну — «SAMPLE/PRELOAD». В версии стандарта 1990 года эти инструкции были жёстко объединены, но в версии 2001 их разделили с возможностью задания им одного и того же кода инструкции, либо различных кодов. Работа с регистром инструкций отличается от работы с регистром «BOUNDARY» только тем, что используется ветка «IR» конечного автомата, а не ветка «DR». В остальном — по второму восходящему фронту в состоянии «CAPTURE IR» в параллельную секцию регистра инструкций закладывается значение по умолчанию (прописываемое в атрибуте «INSTRUCTION_CAPTURE» файла BSDL), а по нисходящему фронту в состоянии «UPDATE IR» происходит копирование данных из последовательной секции в параллельную и новая инструкция вступает в силу. Зная основные механизмы работы модуля JTAG, перейдём к написанию кода. Этот код будет запускаться на демоплате «NUCLEO-F103RB», у которой есть один пользовательский светодиод (вывод PA5) и одна пользовательская кнопка (вывод PC13). Часть элементов кода будет повторять пример из первой статьи цикла. Но в целом, кода будет заметно больше. Итак. Определим задействуемые выводы демоплаты: И проведём инициализацию и настройку этих выводов, дополнив функцию «MX_GPIO_Init»: Возьмём «очищенную» функцию «main»: Объявим перед функцией «main» пару массивов («led» и «btn») из одного элемента (по количеству светодиодов и кнопок), а в самой функции пачку переменных, отвечающих за отдельные линии интерфейса JTAG. Кроме этого добавим код для определения фронтов на линии TCK: Теперь добавим в бесконечный цикл (перед детектором фронтов) код, позволяющий в любой (ну, почти любой) момент считать/установить по значению переменных, логические значения на линиях микросхемы: Добавим в детектор фронтов код, определяющий восходящий и нисходящий фронты: ...и создадим две дополнительные функции: Функции несколько отличаются, так как значения на линии TDO меняются только по нисходящему фронту, соответственно «tck_rise» не нуждается в «tdo». Добавим чуть ниже массивов «led» и «btn» глобальное перечисление с состояниями конечного автомата: Нам потребуется функция, целиком отвечающая за переключение состояний автомата. Структура «switch-case» в этой функции идентична такой же структуре из первой статьи цикла:  Добавим в «tck_rise» функцию «fsm_state_change», а также структуру «switch-case», которая будет отвечать за все действия, происходящие по восходящему фронту: Добавим похожую структуру «switch-case» и в функцию «tck_fall»: Добавим к глобальным переменным четыре регистра, вместе с указанием длины, значениями по умолчанию, параллельными секциями (там, где это нужно) и перечислением кодов операций (для регистра инструкций): В функции «tck_rise» заполним состояния для регистра инструкций. А именно, добавим заполнение последовательной секции регистра инструкций значением по умолчанию в состоянии «CAPTURE IR», а также реализуем сдвиг последовательной секции в состоянии «SHIFT IR»: Заполним также состояния для регистра инструкций в функции «tck_fall». В состоянии «TEST LOGIC RESET» будет происходить инициализация обеих секций регистра значением по умолчанию, в состоянии «SHIFT IR» будет происходить установка значений на выводе TDO, в состоянии «UPDATE IR» будет происходить копирование данных из последовательной секции в параллельную: Вернёмся к функции «tck_rise» и заполним состояния для регистра данных. В каждом состоянии, относящемуся к регистру данных необходимо будет рассмотреть возможность подключения любого из подключаемых регистров. А значит нам потребуется ещё одна, вложенная структура «switch-case». Зависеть она будет от содержимого параллельной секции регистра инструкций, а её отдельными случаями будут коды инструкций. Программный код в состоянии «SHIFT DR» для каждого подключаемого регистра будет полностью аналогичен коду в состоянии «SHIFT IR», за исключением названия регистров (и их длины). В «CAPTURE DR» принципиальные отличия будут только для регистра «BOUNDARY» — в его последовательную секцию будет загружаться не значение по умолчанию, а значения соответствующих входов-выходов микросхемы: В функции «tck_fall» состояние «SHIFT DR» полностью аналогично состоянию «SHIFT IR» (с поправкой на названия регистров): Так как подключаемые регистры «DEVICE ID» и «BYPASS» не нуждаются в параллельной секции, то состояние «UPDATE DR» будет описывать только копирование из секции в секцию для регистра «BOUNDARY»: Остался последний штрих — выдача значений из регистра «BOUNDARY» на выходы микросхемы в случае, если в регистре инструкций находится код инструкции «EXTEST». Данный код мы впишем в бесконечный цикл функции «main» перед детектором фронтов:  Скомпилируем и пошьём данный код в «STM32F103RB». Затем соединим его навесными проводами с отладчиком JTAG на базе «FT2232» и запустим «TopJTAG Probe». По аналогии с первой статьёй проверим подключение отладчика к компьютеру и выставим в нём частоту TCK 10кГц. После этого проверим соединение с микросхемой — должен считаться идентификационный номер «0AA55003». Если номер считан, всё подключено правильно и можно создать в «TopJTAG Probe» при помощи мастера проект. Очень странная концепция, но в «TopJTAG Probe» уже созданный проект возможно открыть без физического подключения программатора и микросхемы с модулем JTAG, а вот создать новый без физического подключения — не получится. На последней, третей странице мастера будет предложено подключить файл BSDL. Немного подправив проект файла BSDL (количество выводов и атрибуты регистра «BOUNDARY») из прошлой статьи, мы сможем предложить «TopJTAG Probe» достойный файл BSDL :)  После создания проекта остаётся только запустить коммуникацию по протоколу JTAG. Для этого:  Добавим линию «BTN» к списку для отображения временны́х диаграмм.  Откроем окно выбора инструкций.  Выберем режим работы с инструкцией «EXTEST»  Нажмём кнопку запуска коммуникации.  Теперь, нажимая кнопку на демоплате мы будем видеть в реальном времени изменение на временной диаграмме и мигание соответствующего вывода на схеме.  А также, выбирая в контекстном меню (или комбинацией клавиш) пункт «Toggle Value» на выделенном выходе микросхемы (в таблице слева), мы сможем мигать пользовательским светодиодом. Напишем теперь тот же функционал на «SystemVerilog». В качестве демоплаты будет использоваться плата «DE-Nano». Она имеет 2 пользовательские тактовые кнопки и 8 светодиодов, и мы задействуем их всех. Создадим сперва модуль верхнего уровня и зададим ему входы и выходы:  Далее создадим необходимые нам регистры, с указанием их длины, значений по умолчанию, с последовательными и параллельными секциями и так далее: Добавим после этого перечисление состояний конечного автомата: Создадим два блока «always». Один будет срабатывать по восходящему фронту TCK, другой — по нисходящему: В блок восходящего фронта впишем структуру «case», непосредственно управляющую переключениями конечного автомата: В том же блоке «always» добавим структуру «case» для управления регистрами и вставим в ветвь «CAPTURE IR» код, инициализирующий регистр инструкций, а в ветвь «SHIFT IR» код, осуществляющий сдвиг регистра инструкций: Не углубляясь в синтаксис языков HDL в общем и SystemVerilog-а в частности, отмечу, что существует два типа присваивание: блокирующие «=» и неблокирующее «<=». Во всём данном примере будут использоваться неблокирующие присваивания. Подобно примеру на «Си», в ветвях «CAPTURE DR» и «SHIFT DR» потребуется вложенная структура «case», которая бы задействовала тот или иной подключаемый регистр в зависимости выбранной инструкции. Для написанного выше кода — это будет выглядеть так: Теперь перейдём в блок «always», отвечающий за реакцию на нисходящий фронт, добавим туда структуру «case» и ветвь «SHIFT IR» выдачей бита из регистра инструкций на выход TDO, ветвь «UPDATE IR» — копированием данных последовательной секции регистра инструкций в параллельную секцию, а также инициализацией параллельной секции регистра инструкций в состоянии «TEST LOGIC RESET»: Дополним ветви «SHIFT DR» и «UPDATE DR» кодом, осуществляющим выдачу данных в линию TDO из подключаемых регистров, а также кодом, производящим обновление данных в параллельной секции регистра граничного сканирования: Наконец, установим связь между битами регистра граничного сканирования и выводами, управляющими светодиодами. Для этого вне блоков «always» следует написать код непрерывного присваивания с условным оператором «X ? Y : Z»:  Для тестирования «в железе» данного кода следует слегка видоизменить файл BSDL, увеличив количество входов и выходов в заголовке, а также в описании корпуса, увеличить длину регистра «BOUNDARY» и дополнить описание данного регистра.  После соединения «DE0-Nano» с отладчиком JTAG, созданием проекта в «TopJTAG Probe» и подключения файла BSDL, мы сможем приблизительно вот так управлять светодиодами и считывать состояния кнопок на демонстрационной плате: Как и любая крупная область знаний, как и любой популярный инструмент, JTAG не является герметичной сущностью и имеет рад ветвящихся направлений. Так, кроме тестирования связей двух микросхем, имеющих по одному модулю JTAG возможно тестирование связи микросхемы с JTAG-ом и микросхемы памяти, не имеющей своего модуля JTAG. В таком случае, при помощи управления через JTAG отдельными выводами микросхемы, в память записываются определённые тестовые данные, а затем происходит попытка эти данные считать. Для подобных операций необходимо подняться на более высокие уровни абстракции — на уровень файлов SVF или ещё выше, на уровень специального (проприетарного) ПО, содержащего библиотеки протоколов обмена данными с широкой номенклатурой памятей и иных микросхемы. Другой широкой областью является программирование и отладка через JTAG. Дополнительные подключаемые регистры, а также нестандартные и специфические для каждого производителя микросхем дополнительные инструкции модулей JTAG создают достаточно большой пласт информации, выходящий за рамки базового стандарта IEEE1149.1. Помимо этого, сам стандарт непрерывно совершенствуется, одни его части отмирают, другие — привносятся. В частности, есть версия двухпроводного JTAG, полная своими особенностями и подводными камнями. Иными словами, всегда имеется масса возможностей написать про JTAG ещё что-нибудь такое, что ещё никем не было написано в должном объёме или в должной форме. Я же выражаю надежду, что данный небольшой цикл статей достаточно полно описал базовую часть работы протокола JTAG и позволил кому-то, ранее знакомому с JTAG лишь понаслышке, ощутить чувство инсайта от более полного понимания его работы.