Разглядывая JTAG: что внутри?
Разглядывая 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 DR», «UPDATE XX» выполняется на нисходящем фронте. Это может вызвать вопрос — зачем так было сделано? Почему не сделать единообразно? Ведь гонку фронтов здесь, на первый взгляд, никак не устроить ввиду того, что у всех микросхем цепи JTAG общая линия TMS, а следовательно все микросхемы всегда находятся в одном и том же состоянии.
Причина — предусмотренная стандартом, весьма оригинальная возможность соединения микросхем в цепочку JTAG с общей для всех микросхем линией TCK, но с индивидуальными линиями TMS!
Схема из стандарта IEEE1149.1–2001
Технически, подобное соединение способно несколько ускорить процесс тестирования соединений на плате между микросхемами (собственно, для этого 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».
Выбор инструкции «PRELOAD» и загрузка начального значения в регистр «BOUNDARY»Выбор инструкции «EXTEST» и загрузка следующего значения в регистр «BOUNDARY»
Так как инструкция «SAMPLE» предусматривает сброс данных в последовательную секцию регистра «BOUNDARY», а затем их выдвижение через TDO, а «PRELOAD» — задвигание данных в последовательную секцию через TDI, а затем сброс в параллельную секцию (к тому же «выдвижение» и «задвигание» это, по сути, один и тот же процесс сдвига), то эти две инструкции можно объединить в одну — «SAMPLE/PRELOAD». В версии стандарта 1990 года эти инструкции были жёстко объединены, но в версии 2001 их разделили с возможностью задания им одного и того же кода инструкции, либо различных кодов.
Работа с регистром инструкций отличается от работы с регистром «BOUNDARY» только тем, что используется ветка «IR» конечного автомата, а не ветка «DR». В остальном — по второму восходящему фронту в состоянии «CAPTURE IR» в параллельную секцию регистра инструкций закладывается значение по умолчанию (прописываемое в атрибуте «INSTRUCTION_CAPTURE» файла BSDL), а по нисходящему фронту в состоянии «UPDATE IR» происходит копирование данных из последовательной секции в параллельную и новая инструкция вступает в силу.
Пример ведомого устройства JTAG на Си
Зная основные механизмы работы модуля JTAG, перейдём к написанию кода. Этот код будет запускаться на демоплате «NUCLEO-F103RB», у которой есть один пользовательский светодиод (вывод PA5) и одна пользовательская кнопка (вывод PC13). Часть элементов кода будет повторять пример из первой статьи цикла. Но в целом, кода будет заметно больше.
Итак.
Определим задействуемые выводы демоплаты:
#define JTMS_PIN GPIO_PIN_1
#define JTCK_PIN GPIO_PIN_15
#define JTDI_PIN GPIO_PIN_14
#define JTDO_PIN GPIO_PIN_13
#define LED0_PIN GPIO_PIN_5
#define BTN0_PIN GPIO_PIN_13
И проведём инициализацию и настройку этих выводов, дополнив функцию «MX_GPIO_Init»:
//==== JTAG PINS INIT BEGIN ====
GPIO_InitStruct.Pin = JTMS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = JTCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = JTDI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = JTDO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//==== JTAG PINS INIT END ====
//==== I/O PINS INIT BEGIN ====
GPIO_InitStruct.Pin = BTN0_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LED0_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//==== I/O PINS INIT END ====
Возьмём «очищенную» функцию «main»:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
//Код
}
}
Объявим перед функцией «main» пару массивов («led» и «btn») из одного элемента (по количеству светодиодов и кнопок), а в самой функции пачку переменных, отвечающих за отдельные линии интерфейса JTAG. Кроме этого добавим код для определения фронтов на линии TCK:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
char tms_curr = 1;
char tck_curr = 0;
char tck_prev = 0;
char tdi_curr = 0;
char tdo_curr = 0;
while (1)
{
//Код для «моментального» изменения чего-либо
if(tck_curr != tck_prev)
{
//Код, выполняемый при прохождении фронта
}
tck_prev = tck_curr;
}
}
Теперь добавим в бесконечный цикл (перед детектором фронтов) код, позволяющий в любой (ну, почти любой) момент считать/установить по значению переменных, логические значения на линиях микросхемы:
tms_curr = (GPIOB->IDR & JTMS_PIN) ? 1 : 0;
tck_curr = (GPIOB->IDR & JTCK_PIN) ? 1 : 0;
tdi_curr = (GPIOB->IDR & JTDI_PIN) ? 1 : 0;
if(tdo_curr) GPIOB->ODR |= JTDO_PIN;
else GPIOB->ODR &= ~JTDO_PIN;
btn[0] = (GPIOC->IDR & BTN0_PIN ) ? 1 : 0;
if(led[0]) GPIOA->ODR |= LED0_PIN;
else GPIOA->ODR &= ~LED0_PIN;
Добавим в детектор фронтов код, определяющий восходящий и нисходящий фронты:
if(tck_curr) tck_rise(tms_curr, tdi_curr);
else tdo_curr = tck_fall(tms_curr, tdi_curr);
…и создадим две дополнительные функции:
void tck_rise(char tms, char tdi)
{
}
char tck_fall(char tms, char tdi)
{
char tdo = 0; // HiZ
return tdo;
}
Функции несколько отличаются, так как значения на линии TDO меняются только по нисходящему фронту, соответственно «tck_rise» не нуждается в «tdo».
Добавим чуть ниже массивов «led» и «btn» глобальное перечисление с состояниями конечного автомата:
enum Jtag_fsm{
TEST_LOGIC_RESET,
RUN_TEST_IDLE,
SELECT_DR_SCAN,
CAPTURE_DR,
SHIFT_DR,
EXIT1_DR,
PAUSE_DR,
EXIT2_DR,
UPDATE_DR,
SELECT_IR_SCAN,
CAPTURE_IR,
SHIFT_IR,
EXIT1_IR,
PAUSE_IR,
EXIT2_IR,
UPDATE_IR
} jtag_fsm = TEST_LOGIC_RESET;
Нам потребуется функция, целиком отвечающая за переключение состояний автомата. Структура «switch-case» в этой функции идентична такой же структуре из первой статьи цикла:
void fsm_state_change(char tms)
{
switch(jtag_fsm){
case TEST_LOGIC_RESET:
if(tms) jtag_fsm = TEST_LOGIC_RESET;
else jtag_fsm = RUN_TEST_IDLE;
break;
case RUN_TEST_IDLE:
if(tms) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
<...>
case UPDATE_IR:
if(tms) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
}
}
Полный код функции
void fsm_state_change(char tms)
{
switch(jtag_fsm){
case TEST_LOGIC_RESET:
if(tms_curr) jtag_fsm = TEST_LOGIC_RESET;
else jtag_fsm = RUN_TEST_IDLE;
break;
case RUN_TEST_IDLE:
if(tms_curr) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
case SELECT_DR_SCAN:
if(tms_curr) jtag_fsm = SELECT_IR_SCAN;
else jtag_fsm = CAPTURE_DR;
break;
case CAPTURE_DR:
if(tms_curr) jtag_fsm = EXIT1_DR;
else jtag_fsm = SHIFT_DR;
break;
case SHIFT_DR:
if(tms_curr) jtag_fsm = EXIT1_DR;
else jtag_fsm = SHIFT_DR;
break;
case EXIT1_DR:
if(tms_curr) jtag_fsm = UPDATE_DR;
else jtag_fsm = PAUSE_DR;
break;
case PAUSE_DR:
if(tms_curr) jtag_fsm = EXIT2_DR;
else jtag_fsm = PAUSE_DR;
break;
case EXIT2_DR:
if(tms_curr) jtag_fsm = UPDATE_DR;
else jtag_fsm = SHIFT_DR;
break;
case UPDATE_DR:
if(tms_curr) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
case SELECT_IR_SCAN:
if(tms_curr) jtag_fsm = TEST_LOGIC_RESET;
else jtag_fsm = CAPTURE_IR;
break;
case CAPTURE_IR:
if(tms_curr) jtag_fsm = EXIT1_IR;
else jtag_fsm = SHIFT_IR;
break;
case SHIFT_IR:
if(tms_curr) jtag_fsm = EXIT1_IR;
else jtag_fsm = SHIFT_IR;
break;
case EXIT1_IR:
if(tms_curr) jtag_fsm = UPDATE_IR;
else jtag_fsm = PAUSE_IR;
break;
case PAUSE_IR:
if(tms_curr) jtag_fsm = EXIT2_IR;
else jtag_fsm = PAUSE_IR;
break;
case EXIT2_IR:
if(tms_curr) jtag_fsm = UPDATE_IR;
else jtag_fsm = SHIFT_IR;
break;
case UPDATE_IR:
if(tms_curr) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
}
}
Добавим в «tck_rise» функцию «fsm_state_change», а также структуру «switch-case», которая будет отвечать за все действия, происходящие по восходящему фронту:
void tck_rise(char tms, char tdi)
{
switch(jtag_fsm){
case CAPTURE_DR:
//
break;
case SHIFT_DR:
//
break;
case CAPTURE_IR:
//
break;
case SHIFT_IR:
//
break;
}
fsm_state_change(tms);
}
Добавим похожую структуру «switch-case» и в функцию «tck_fall»:
char tck_fall(char tms, char tdi)
{
char tdo = 0; // HiZ
switch(jtag_fsm){
case TEST_LOGIC_RESET:
//
break;
case SHIFT_DR:
//
break;
case EXIT1_DR:
tdo = 0; // HiZ
break;
case UPDATE_DR:
//
break;
case SHIFT_IR:
//
break;
case EXIT1_IR:
tdo = 0; // HiZ
break;
case UPDATE_IR:
//
break;
}
return tdo;
}
Добавим к глобальным переменным четыре регистра, вместе с указанием длины, значениями по умолчанию, параллельными секциями (там, где это нужно) и перечислением кодов операций (для регистра инструкций):
//Регистр инструкций
uint8_t instruction_reg;
uint8_t instruction_reg_par;
const uint8_t INSTRUCTION_LENGTH = 3;
const uint8_t INSTRUCTION_DEFAULT = 0x01;
enum Instructions{
INST_IDCODE = 0x01,
INST_EXTEST = 0x02,
INST_BYPASS = 0x03,
INST_SAMPLE = 0x04
};
//Регистр идентификационного кода
uint32_t device_id_reg;
const uint8_t DEVICE_ID_LENGTH = 32;
const uint32_t DEVICE_ID_DEFAULT = 0x0AA55003;
//Регистр BYPASS из одного бита
uint8_t bypass_reg;
const uint8_t BYPASS_LENGTH = 1;
const uint8_t BYPASS_DEFAULT = 0;
//Регистр граничного сканирования
uint32_t boundary_reg;
uint32_t boundary_reg_par;
const uint8_t BOUNDARY_LENGTH = 2;
В функции «tck_rise» заполним состояния для регистра инструкций. А именно, добавим заполнение последовательной секции регистра инструкций значением по умолчанию в состоянии «CAPTURE IR», а также реализуем сдвиг последовательной секции в состоянии «SHIFT IR»:
case CAPTURE_IR:
instruction_reg = INSTRUCTION_DEFAULT;
break;
case SHIFT_IR:
instruction_reg = (instruction_reg >> 1) | (uint32_t)(tdi << (INSTRUCTION_LENGTH - 1));
break;
Заполним также состояния для регистра инструкций в функции «tck_fall». В состоянии «TEST LOGIC RESET» будет происходить инициализация обеих секций регистра значением по умолчанию, в состоянии «SHIFT IR» будет происходить установка значений на выводе TDO, в состоянии «UPDATE IR» будет происходить копирование данных из последовательной секции в параллельную:
case TEST_LOGIC_RESET:
instruction_reg = INSTRUCTION_DEFAULT;
instruction_reg_par = INSTRUCTION_DEFAULT;
tdo = 0; // HiZ
break;
<...>
case SHIFT_IR:
tdo = instruction_reg & ((uint8_t)0x1);
break;
<...>
case UPDATE_IR:
instruction_reg_par = instruction_reg;
break;
Вернёмся к функции «tck_rise» и заполним состояния для регистра данных. В каждом состоянии, относящемуся к регистру данных необходимо будет рассмотреть возможность подключения любого из подключаемых регистров. А значит нам потребуется ещё одна, вложенная структура «switch-case». Зависеть она будет от содержимого параллельной секции регистра инструкций, а её отдельными случаями будут коды инструкций. Программный код в состоянии «SHIFT DR» для каждого подключаемого регистра будет полностью аналогичен коду в состоянии «SHIFT IR», за исключением названия регистров (и их длины). В «CAPTURE DR» принципиальные отличия будут только для регистра «BOUNDARY» — в его последовательную секцию будет загружаться не значение по умолчанию, а значения соответствующих входов-выходов микросхемы:
case CAPTURE_DR:
switch(instruction_reg_par){
case INST_IDCODE:
device_id_reg = DEVICE_ID_DEFAULT;
break;
case INST_BYPASS:
bypass_reg = BYPASS_DEFAULT;
break;
case INST_SAMPLE:
boundary_reg = (led[0] << 0) | (btn[0] << 1);
break;
case INST_EXTEST:
boundary_reg = (led[0] << 0) | (btn[0] << 1);
break;
}
break;
case SHIFT_DR:
switch(instruction_reg_par){
case INST_IDCODE:
device_id_reg = (device_id_reg >> 1) | (uint32_t)(tdi << (DEVICE_ID_LENGTH - 1));
break;
case INST_BYPASS:
bypass_reg = (bypass_reg >> 1) | (uint32_t)(tdi << (BYPASS_LENGTH - 1));
break;
case INST_SAMPLE:
boundary_reg = (boundary_reg >> 1) | (uint32_t)(tdi << (BOUNDARY_LENGTH - 1));
break;
case INST_EXTEST:
boundary_reg = (boundary_reg >> 1) | (uint32_t)(tdi << (BOUNDARY_LENGTH - 1));
break;
}
break;
В функции «tck_fall» состояние «SHIFT DR» полностью аналогично состоянию «SHIFT IR» (с поправкой на названия регистров):
case SHIFT_DR:
switch(instruction_reg_par){
case INST_IDCODE:
tdo = device_id_reg & ((uint32_t)0x0000001);
break;
case INST_BYPASS:
tdo = bypass_reg & ((uint8_t)0x01);
break;
case INST_SAMPLE:
tdo = boundary_reg & ((uint8_t)0x01);
break;
case INST_EXTEST:
tdo = boundary_reg & ((uint8_t)0x01);
break;
}
break;
Так как подключаемые регистры «DEVICE ID» и «BYPASS» не нуждаются в параллельной секции, то состояние «UPDATE DR» будет описывать только копирование из секции в секцию для регистра «BOUNDARY»:
case UPDATE_DR:
switch(instruction_reg){
case INST_SAMPLE:
boundary_reg_par = boundary_reg;
break;
case INST_EXTEST:
boundary_reg_par = boundary_reg;
break;
}
break;
Остался последний штрих — выдача значений из регистра «BOUNDARY» на выходы микросхемы в случае, если в регистре инструкций находится код инструкции «EXTEST». Данный код мы впишем в бесконечный цикл функции «main» перед детектором фронтов:
if(instruction_reg_par == INST_EXTEST)
{
led[0] = boundary_reg_par & (uint8_t)(0x01 << 0);
}
else
{
led[0] = 0; //HiZ
}
Код на Си полностью
#include "main.h"
#define JTMS_PIN GPIO_PIN_1
#define JTCK_PIN GPIO_PIN_15
#define JTDI_PIN GPIO_PIN_14
#define JTDO_PIN GPIO_PIN_13
#define LED0_PIN GPIO_PIN_5
#define BTN0_PIN GPIO_PIN_13
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
uint8_t instruction_reg;
uint8_t instruction_reg_par;
const uint8_t INSTRUCTION_LENGTH = 3;
const uint8_t INSTRUCTION_DEFAULT = 0x01;
enum Instructions{
INST_IDCODE = 0x01,
INST_EXTEST = 0x02,
INST_BYPASS = 0x03,
INST_SAMPLE = 0x04
};
uint32_t device_id_reg;
const uint8_t DEVICE_ID_LENGTH = 32;
const uint32_t DEVICE_ID_DEFAULT = 0x0AA55003;
uint8_t bypass_reg;
const uint8_t BYPASS_LENGTH = 1;
const uint8_t BYPASS_DEFAULT = 0;
uint32_t boundary_reg;
uint32_t boundary_reg_par;
const uint8_t BOUNDARY_LENGTH = 2;
uint8_t led[1];
uint8_t btn[1];
enum Jtag_fsm{
TEST_LOGIC_RESET,
RUN_TEST_IDLE,
SELECT_DR_SCAN,
CAPTURE_DR,
SHIFT_DR,
EXIT1_DR,
PAUSE_DR,
EXIT2_DR,
UPDATE_DR,
SELECT_IR_SCAN,
CAPTURE_IR,
SHIFT_IR,
EXIT1_IR,
PAUSE_IR,
EXIT2_IR,
UPDATE_IR
} jtag_fsm = TEST_LOGIC_RESET;
void fsm_state_change(char tms)
{
switch(jtag_fsm){
case TEST_LOGIC_RESET:
if(tms) jtag_fsm = TEST_LOGIC_RESET;
else jtag_fsm = RUN_TEST_IDLE;
break;
case RUN_TEST_IDLE:
if(tms) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
case SELECT_DR_SCAN:
if(tms) jtag_fsm = SELECT_IR_SCAN;
else jtag_fsm = CAPTURE_DR;
break;
case CAPTURE_DR:
if(tms) jtag_fsm = EXIT1_DR;
else jtag_fsm = SHIFT_DR;
break;
case SHIFT_DR:
if(tms) jtag_fsm = EXIT1_DR;
else jtag_fsm = SHIFT_DR;
break;
case EXIT1_DR:
if(tms) jtag_fsm = UPDATE_DR;
else jtag_fsm = PAUSE_DR;
break;
case PAUSE_DR:
if(tms) jtag_fsm = EXIT2_DR;
else jtag_fsm = PAUSE_DR;
break;
case EXIT2_DR:
if(tms) jtag_fsm = UPDATE_DR;
else jtag_fsm = SHIFT_DR;
break;
case UPDATE_DR:
if(tms) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
case SELECT_IR_SCAN:
if(tms) jtag_fsm = TEST_LOGIC_RESET;
else jtag_fsm = CAPTURE_IR;
break;
case CAPTURE_IR:
if(tms) jtag_fsm = EXIT1_IR;
else jtag_fsm = SHIFT_IR;
break;
case SHIFT_IR:
if(tms) jtag_fsm = EXIT1_IR;
else jtag_fsm = SHIFT_IR;
break;
case EXIT1_IR:
if(tms) jtag_fsm = UPDATE_IR;
else jtag_fsm = PAUSE_IR;
break;
case PAUSE_IR:
if(tms) jtag_fsm = EXIT2_IR;
else jtag_fsm = PAUSE_IR;
break;
case EXIT2_IR:
if(tms) jtag_fsm = UPDATE_IR;
else jtag_fsm = SHIFT_IR;
break;
case UPDATE_IR:
if(tms) jtag_fsm = SELECT_DR_SCAN;
else jtag_fsm = RUN_TEST_IDLE;
break;
}
}
void tck_rise(char tms, char tdi)
{
switch(jtag_fsm){
case CAPTURE_DR:
switch(instruction_reg_par){
case INST_IDCODE:
device_id_reg = DEVICE_ID_DEFAULT;
break;
case INST_BYPASS:
bypass_reg = BYPASS_DEFAULT;
break;
case INST_SAMPLE:
boundary_reg = (led[0] << 0) | (btn[0] << 1);
break;
case INST_EXTEST:
boundary_reg = (led[0] << 0) | (btn[0] << 1);
break;
}
break;
case SHIFT_DR:
switch(instruction_reg_par){
case INST_IDCODE:
device_id_reg = (device_id_reg >> 1) | (uint32_t)(tdi << (DEVICE_ID_LENGTH - 1));
break;
case INST_BYPASS:
bypass_reg = (bypass_reg >> 1) | (uint32_t)(tdi << (BYPASS_LENGTH - 1));
break;
case INST_SAMPLE:
boundary_reg = (boundary_reg >> 1) | (uint32_t)(tdi << (BOUNDARY_LENGTH - 1));
break;
case INST_EXTEST:
boundary_reg = (boundary_reg >> 1) | (uint32_t)(tdi << (BOUNDARY_LENGTH - 1));
break;
}
break;
case CAPTURE_IR:
instruction_reg = INSTRUCTION_DEFAULT;
break;
case SHIFT_IR:
instruction_reg = (instruction_reg >> 1) | (uint32_t)(tdi << (INSTRUCTION_LENGTH - 1));
break;
}
fsm_state_change(tms);
}
char tck_fall(char tms, char tdi)
{
char tdo = 0; // HiZ
switch(jtag_fsm){
case TEST_LOGIC_RESET:
instruction_reg = INSTRUCTION_DEFAULT;
instruction_reg_par = INSTRUCTION_DEFAULT;
tdo = 0; // HiZ
break;
case SHIFT_DR:
switch(instruction_reg_par){
case INST_IDCODE:
tdo = device_id_reg & ((uint32_t)0x0000001);
break;
case INST_BYPASS:
tdo = bypass_reg & ((uint8_t)0x01);
break;
case INST_SAMPLE:
tdo = boundary_reg & ((uint8_t)0x01);
break;
case INST_EXTEST:
tdo = boundary_reg & ((uint8_t)0x01);
break;
}
break;
case EXIT1_DR:
tdo = 0; // HiZ
break;
case UPDATE_DR:
switch(instruction_reg){
case INST_SAMPLE:
boundary_reg_par = boundary_reg;
break;
case INST_EXTEST:
boundary_reg_par = boundary_reg;
break;
}
break;
case SHIFT_IR:
tdo = instruction_reg & ((uint8_t)0x1);
break;
case EXIT1_IR:
tdo = 0; // HiZ
break;
case UPDATE_IR:
instruction_reg_par = instruction_reg;
break;
}
return tdo;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
char tms_curr = 1;
char tck_curr = 0;
char tck_prev = 0;
char tdi_curr = 0;
char tdo_curr = 0;
while (1)
{
tms_curr = (GPIOB->IDR & JTMS_PIN ) ? 1 : 0;
tck_curr = (GPIOB->IDR & JTCK_PIN) ? 1 : 0;
tdi_curr = (GPIOB->IDR & JTDI_PIN) ? 1 : 0;
if(tdo_curr) GPIOB->ODR |= JTDO_PIN;
else GPIOB->ODR &= ~JTDO_PIN;
btn[0] = (GPIOC->IDR & BTN0_PIN ) ? 1 : 0;
if(led[0]) GPIOA->ODR |= LED0_PIN;
else GPIOA->ODR &= ~LED0_PIN;
if(instruction_reg_par == INST_EXTEST)
{
led[0] = boundary_reg_par & (uint8_t)(0x01 << 0);
}
else
{
led[0] = 0; //HiZ
}
if(tck_curr != tck_prev)
{
if(tck_curr) tck_rise(tms_curr, tdi_curr);
else tdo_curr = tck_fall(tms_curr, tdi_curr);
}
tck_prev = tck_curr;
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
//==== JTAG PINS INIT BEGIN ====
GPIO_InitStruct.Pin = JTMS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = JTCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = JTDI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = JTDO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//==== JTAG PINS INIT END ====
//==== I/O PINS INIT BEGIN ====
GPIO_InitStruct.Pin = BTN0_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LED0_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//==== I/O PINS INIT END ====
}
Тестирование «в железе»
Скомпилируем и пошьём данный код в «STM32F103RB». Затем соединим его навесными проводами с отладчиком JTAG на базе «FT2232» и запустим «TopJTAG Probe». По аналогии с первой статьёй проверим подключение отладчика к компьютеру и выставим в нём частоту TCK 10кГц. После этого проверим соединение с микросхемой — должен считаться идентификационный номер »0AA55003». Если номер считан, всё подключено правильно и можно создать в «TopJTAG Probe» при помощи мастера проект. Очень странная концепция, но в «TopJTAG Probe» уже созданный проект возможно открыть без физического подключения программатора и микросхемы с модулем JTAG, а вот создать новый без физического подключения — не получится.
На последней, третей странице мастера будет предложено подключить файл BSDL. Немного подправив проект файла BSDL (количество выводов и атрибуты регистра «BOUNDARY») из прошлой статьи, мы сможем предложить «TopJTAG Probe» достойный файл BSDL:)
Текст файла BSDL
entity MY_IC is
generic (PHYSICAL_PIN_MAP : string);
port (
LED: out bit;
BTN: in bit;
TMS: in bit;
TDI: in bit;
TCK: in bit;
TDO: out bit;
VDD: linkage bit;
VSS: linkage bit;
NC: linkage bit_vector(0 to 12)
);
use STD_1149_1_2001.all;
attribute COMPONENT_CONFORMANCE of MY_IC : entity is "STD_1149_1_2001";
attribute PIN_MAP of MY_IC : entity is PHYSICAL_PIN_MAP;
constant PLCC20:PIN_MAP_STRING:=
"LED: 1, " &
"BTN: 2, " &
"TMS: 3, " &
"TDI: 4, " &
"TCK: 5, " &
"TDO: 6, " &
"VDD: 7, " &
"VSS: 8, " &
"NC: (9,10,11,12,13,14,15,16,17,18,19,20) ";
attribute TAP_SCAN_MODE of TMS : signal is true;
attribute TAP_SCAN_IN of TDI : signal is true;
attribute TAP_SCAN_CLOCK of TCK : signal is (10.0e3, BOTH);
attribute TAP_SCAN_OUT of TDO : signal is true;
attribute INSTRUCTION_LENGTH of MY_IC : entity is 3;
attribute INSTRUCTION_OPCODE of MY_IC : entity is
"IDCODE (001)," &
"EXTEST (010)," &
"BYPASS (011)," &
"SAMPLE (100) ";
attribute INSTRUCTION_CAPTURE of MY_IC : entity is "00000001";
attribute IDCODE_REGISTER of MY_IC : entity is
"0000" & -- код ревизии или чего-нибудь типа того
"1010101001010101" & -- код модели микросхемы hAA55
"00000000001" & -- код производителя (соответствует AMD)
"1"; -- единица по стандарту IEEE1149.1
attribute REGISTER_ACCESS of MY_IC : entity is
"DEVICE_ID (IDCODE)," &
"BYPASS (BYPASS)," &
"BOUNDARY (EXTEST, SAMPLE)";
attribute BOUNDARY_LENGTH of MY_IC : entity is 2;
attribute BOUNDARY_REGISTER of MY_IC : entity is
"0 (BC_1, LED, output2, X)," &
"1 (BC_1, BTN, input, X)" ;
end MY_IC;
После создания проекта остаётся только запустить коммуникацию по протоколу JTAG. Для этого:
Добавим линию «BTN» к списку для отображения временны́х диаграмм.
Откроем окно выбора инструкций.
Выберем режим работы с инструкцией «EXTEST»
Нажмём кнопку запуска коммуникации.
Теперь, нажимая кнопку на демоплате мы будем видеть в реальном времени изменение на временной диаграмме и мигание соответствующего вывода на схеме.
А также, выбирая в контекстном меню (или комбинацией клавиш) пункт «Toggle Value» на выделенном выходе микросхемы (в таблице слева), мы сможем мигать пользовательским светодиодом.
Пример ведомого устройства JTAG на SystemVerilog
Напишем теперь тот же функционал на «SystemVerilog». В качестве демоплаты будет использоваться плата «DE-Nano». Она имеет 2 пользовательские тактовые кнопки и 8 светодиодов, и мы задействуем их всех.
Создадим сперва модуль верхнего уровня и зададим ему входы и выходы:
module main (
input tms,
input tck,
input tdi,
output reg tdo,
output reg [7:0]led,
input [1:0]btn
);
//Код
endmodule
Список используемых выводов в DE0-Nano
btn[0] -> J15
btn[1] -> E1
led[0] -> A15
led[1] -> A13
led[2] -> B13
led[3] -> A11
led[4] -> D1
led[5] -> F3
led[6] -> B1
led[7] -> L3
tms -> E10
tck -> B11
tdi -> D11
tdo -> B12
Далее создадим необходимые нам регистры, с указанием их длины, значений по умолчанию, с последовательными и параллельными секциями и так далее:
//Регистр инструкций
parameter INSTRUCTION_LENGTH = 3;
parameter INSTRUCTION_DEFAULT = 3'b001;
reg [INSTRUCTION_LENGTH-1:0]instruction_ser;
reg [INSTRUCTION_LENGTH-1:0]instruction_par;
enum reg[INSTRUCTION_LENGTH-1:0]{
INST_IDCODE = 3'b001,
INST_EXTEST = 3'b010,
INST_BYPASS = 3'b011,
INST_SAMPLE = 3'b100
} instructions;
//Регистр идентификационного кода
parameter DEVICE_ID_LENGTH = 32;
parameter DEVICE_ID_DEFAULT = 32'h0AA55003;
reg [DEVICE_ID_LENGTH-1:0]device_id_ser;
//Регистр BYPASS из одного бита
parameter BYPASS_DEFAULT = 0;
reg bypass_ser;
//Регистр граничного сканирования
parameter BOUNDARY_LENGTH = 10;
reg [BOUNDARY_LENGTH-1:0]boundary_ser;
reg [BOUNDARY_LENGTH-1:0]boundary_par;
Добавим после этого перечисление состояний конечного автомата:
enum{
TEST_LOGIC_RESET,
RUN_TEST_IDLE,
SELECT_DR_SCAN,
CAPTURE_DR,
SHIFT_DR,
EXIT1_DR,
PAUSE_DR,
EXIT2_DR,
UPDATE_DR,
SELECT_IR_SCAN,
CAPTURE_IR,
SHIFT_IR,
EXIT1_IR,
PAUSE_IR,
EXIT2_IR,
UPDATE_IR
} jtag_fsm = TEST_LOGIC_RESET;
Создадим два блока «always». Один будет срабатывать по восходящему фронту TCK, другой — по нисходящему:
always @(posedge tck) begin
//Код
end
always @(negedge tck) begin
//Код
end
В блок восходящего фронта впишем структуру «case», непосредственно управляющую переключениями конечного автомата:
case(jtag_fsm)
TEST_LOGIC_RESET:begin
if(tms) jtag_fsm <= TEST_LOGIC_RESET;
else jtag_fsm <= RUN_TEST_IDLE;
end
RUN_TEST_IDLE:begin
if(tms) jtag_fsm <= SELECT_DR_SCAN;
else jtag_fsm <= RUN_TEST_IDLE;
end
<...>
UPDATE_IR:begin
if(tms) jtag_fsm <= SELECT_DR_SCAN;
else jtag_fsm <= RUN_TEST_IDLE;
end
endcase
В том же блоке «always» добавим структуру «case» для управления регистрами и вставим в ветвь «CAPTURE IR» код, инициализирующий регистр инструкций, а в ветвь «SHIFT IR» код, осуществляющий сдвиг регистра инструкций:
case(jtag_fsm)
CAPTURE_DR:begin
//
end
SHIFT_DR:begin
//
end
CAPTURE_IR:begin
instruction_ser <= INSTRUCTION_DEFAULT;
end
SHIFT_IR:begin
instruction_ser <= {tdi,instruction_ser[INSTRUCTION_LENGTH-1:1]};
end
endcase
Не углубляясь в синтаксис языков HDL в общем и SystemVerilog-а в частности, отмечу, что существует два типа присваивание: блокирующие »=» и неблокирующее »<=». Во всём данном примере будут использоваться неблокирующие присваивания.
Подобно примеру на «Си», в ветвях «CAPTURE DR» и «SHIFT DR» потребуется вложенная структура «case», которая бы задействовала тот или иной подключаемый регистр в зависимости выбранной инструкции. Для написанного выше кода — это будет выглядеть так:
CAPTURE_DR:begin
case(instruction_par)
INST_IDCODE:begin
device_id_ser <= DEVICE_ID_DEFAULT;
end
INST_BYPASS:begin
bypass_ser <= BYPASS_DEFAULT;
end
INST_SAMPLE:begin
boundary_ser[7:0] <= led[7:0];
boundary_ser[9:8] <= btn[1:0];
end
INST_EXTEST:begin
boundary_ser[7:0] <= led[7:0];
boundary_ser[9:8] <= btn[1:0];
end
endcase
end
SHIFT_DR:begin
case(instruction_par)
INST_IDCODE:begin
device_id_ser <= {tdi,device_id_ser[DEVICE_ID_LENGTH-1:1]};
end
INST_BYPASS:begin
bypass_ser <= tdi;
end
INST_SAMPLE:begin
boundary_ser <= {tdi,boundary_ser[BOUNDARY_LENGTH-1:1]};
end
INST_EXTEST:begin
boundary_ser <= {tdi,boundary_ser[BOUNDARY_LENGTH-1:1]};
end
endcase
end
Теперь перейдём в блок «always», отвечающий за реакцию на нисходящий фронт, добавим туда структуру «case» и ветвь «SHIFT IR» выдачей бита из регистра инструкций на выход TDO, ветвь «UPDATE IR» — копированием данных последовательной секции регистра инструкций в параллельную секцию, а также инициализацией параллельной секции регистра инструкций в состоянии «TEST LOGIC RESET»:
case(jtag_fsm)
TEST_LOGIC_RESET:begin
instruction_par <= INSTRUCTION_DEFAULT;
tdo <= 1'bz;//Переключение TDO в состояние HiZ
end
SHIFT_DR:begin
//
end
EXIT1_IR:begin
tdo <= 1'bz;//Переключение TDO в состояние HiZ
end
UPDATE_DR:begin
//
end
SHIFT_IR:begin
tdo <= instruction_ser[0];
end
EXIT1_IR:begin
tdo <= 1'bz;//Переключение TDO в состояние HiZ
end
UPDATE_IR:begin
instruction_par <= instruction_ser;
end
endcase
Дополним ветви «SHIFT DR» и «UPDATE DR» кодом, осуществляющим выдачу данных в линию TDO из подключаемых регистров, а также кодом, производящим обновление данных в параллельной секции регистра граничного сканирования:
SHIFT_DR:begin
case(instruction_par)
INST_IDCODE:begin
tdo <= device_id_ser[0];
end
INST_BYPASS:begin
tdo <= bypass_ser;
end
INST_SAMPLE:begin
tdo <= boundary_ser[0];
end
INST_EXTEST:begin
tdo <= boundary_ser[0];
end
endcase
end
<...>
UPDATE_DR:begin
case(instruction_par)
INST_SAMPLE:begin
boundary_par <= boundary_ser;
end
INST_EXTEST:begin
boundary_par <= boundary_ser;
end
endcase
end
Наконец, установим связь между битами регистра граничного сканирования и выводами, управляющими светодиодами. Для этого вне блоков «always» следует написать код непрерывного присваивания с условным оператором «X? Y: Z»:
assign led = (instruction_par == INST_EXTEST) ? {boundary_par[7:0]} : 8'bzzzz_zzzz;
Код на SystemVerilog полностью
module main (
input tms,
input tck,
input tdi,
output reg tdo,
output reg [7:0]led,
input [1:0]btn
);
parameter INSTRUCTION_LENGTH = 3;
parameter INSTRUCTION_DEFAULT = 3'b001;
reg [INSTRUCTION_LENGTH-1:0]instruction_ser;
reg [INSTRUCTION_LENGTH-1:0]instruction_par;
enum reg[INSTRUCTION_LENGTH-1:0]{
INST_IDCODE = 3'b001,
INST_EXTEST = 3'b010,
INST_BYPASS = 3'b011,
INST_SAMPLE = 3'b100
} instructions;
parameter DEVICE_ID_LENGTH = 32;
parameter DEVICE_ID_DEFAULT = 32'h0AA55003;
reg [DEVICE_ID_LENGTH-1:0]device_id_ser;
parameter BYPASS_DEFAULT = 0;
reg bypass_ser;
parameter BOUNDARY_LENGTH = 10;
reg [BOUNDARY_LENGTH-1:0]boundary_ser;
reg [BOUNDARY_LENGTH-1:0]boundary_par;
enum{
TEST_LOGIC_RESET,
RUN_TEST_IDLE,
SELECT_DR_SCAN,
CAPTURE_DR,
SHIFT_DR,
EXIT1_DR,
PAUSE_DR,
EXIT2_DR,
UPDATE_DR,
SELECT_IR_SCAN,
CAPTURE_IR,
SHIFT_IR,
EXIT1_IR,
PAUSE_IR,
EXIT2_IR,
UPDATE_IR
} jtag_fsm = TEST_LOGIC_RESET;
always @(posedge tck) begin
case(jtag_fsm)
CAPTURE_DR:begin
case(instruction_par)
INST_IDCODE:begin
device_id_ser <= DEVICE_ID_DEFAULT;
end
INST_BYPASS:begin
bypass_ser <= BYPASS_DEFAULT;
end
INST_SAMPLE:begin
boundary_ser[7:0] <= led[7:0];
boundary_ser[9:8] <= btn[1:0];
end
INST_EXTEST:begin
boundary_ser[7:0] <= led[7:0];
boundary_ser[9:8] <= btn[1:0];
end
endcase
end
SHIFT_DR:begin
case(instruction_par)
INST_IDCODE:begin
device_id_ser <= {tdi,device_id_ser[DEVICE_ID_LENGTH-1:1]};
end
INST_BYPASS:begin
bypass_ser <= tdi;
end
INST_SAMPLE:begin
boundary_ser <= {tdi,boundary_ser[BOUNDARY_LENGTH-1:1]};
end
INST_EXTEST:begin
boundary_ser <= {tdi,boundary_ser[BOUNDARY_LENGTH-1:1]};
end
endcase
end
CAPTURE_IR:begin
instruction_ser <= INSTRUCTION_DEFAULT;
end
SHIFT_IR:begin
instruction_ser <= {tdi,instruction_ser[INSTRUCTION_LENGTH-1:1]};
end
endcase
case(jtag_fsm)
TEST_LOGIC_RESET:begin
if(tms) jtag_fsm <= TEST_LOGIC_RESET;
else jtag_fsm <= RUN_TEST_IDLE;
end
RUN_TEST_IDLE:begin
if(tms) jtag_fsm <= SELECT_DR_SCAN;
else jtag_fsm <= RUN_TEST_IDLE;
end
SELECT_DR_SCAN:begin
if(tms) jtag_fsm <= SELECT_IR_SCAN;
else jtag_fsm <= CAPTURE_DR;
end
CAPTURE_DR:begin
if(tms) jtag_fsm <= EXIT1_DR;
else jtag_fsm <= SHIFT_DR;
end
SHIFT_DR:begin
if(tms) jtag_fsm <= EXIT1_DR;
else jtag_fsm <= SHIFT_DR;
end
EXIT1_DR:begin
if(tms) jtag_fsm <= UPDATE_DR;
else jtag_fsm <= PAUSE_DR;
end
PAUSE_DR:begin
if(tms) jtag_fsm <= EXIT2_DR;
else jtag_fsm <= PAUSE_DR;
end
EXIT2_DR:begin
if(tms) jtag_fsm <= UPDATE_DR;
else jtag_fsm <= SHIFT_DR;
end
UPDATE_DR:begin
if(tms) jtag_fsm <= SELECT_DR_SCAN;
else jtag_fsm <= RUN_TEST_IDLE;
end
SELECT_IR_SCAN:begin
if(tms) jtag_fsm <= TEST_LOGIC_RESET;
else jtag_fsm <= CAPTURE_IR;
end
CAPTURE_IR:begin
if(tms) jtag_fsm <= EXIT1_IR;
else jtag_fsm <= SHIFT_IR;
end
SHIFT_IR:begin
if(tms) jtag_fsm <= EXIT1_IR;
else jtag_fsm <= SHIFT_IR;
end
EXIT1_IR:begin
if(tms) jtag_fsm <= UPDATE_IR;
else jtag_fsm <= PAUSE_IR;
end
PAUSE_IR:begin
if(tms) jtag_fsm <= EXIT2_IR;
else jtag_fsm <= PAUSE_IR;
end
EXIT2_IR:begin
if(tms) jtag_fsm <= UPDATE_IR;
else jtag_fsm <= SHIFT_IR;
end
UPDATE_IR:begin
if(tms) jtag_fsm <= SELECT_DR_SCAN;
else jtag_fsm <= RUN_TEST_IDLE;
end
endcase
end
always @(negedge tck) begin
case(jtag_fsm)
TEST_LOGIC_RESET:begin
instruction_par <= INSTRUCTION_DEFAULT;
tdo <= 1'bz;
end
SHIFT_DR:begin
case(instruction_par)
INST_IDCODE:begin
tdo <= device_id_ser[0];
end
INST_BYPASS:begin
tdo <= bypass_ser;
end
INST_SAMPLE:begin
tdo <= boundary_ser[0];
end
INST_EXTEST:begin
tdo <= boundary_ser[0];
end
endcase
end
EXIT1_IR:begin
tdo <= 1'bz;
end
UPDATE_DR:begin
case(instruction_par)
INST_SAMPLE:begin
boundary_par <= boundary_ser;
end
INST_EXTEST:begin
boundary_par <= boundary_ser;
end
endcase
end
SHIFT_IR:begin
tdo <= instruction_ser[0];
end
EXIT1_IR:begin
tdo <= 1'bz;
end
UPDATE_IR:begin
instruction_par <= instruction_ser;
end
endcase
end
assign led = (instruction_par == INST_EXTEST) ? {boundary_par[7:0]} : 8'bzzzz_zzzz;
endmodule
Для тестирования «в железе» данного кода следует слегка видоизменить файл BSDL, увеличив количество входов и выходов в заголовке, а также в описании корпуса, увеличить длину регистра «BOUNDARY» и дополнить описание данного регистра.
Текст файла BSDL
entity MY_IC is
generic (PHYSICAL_PIN_MAP : string);
port (
LED: out bit_vector(0 to 7);
BTN: in bit_vector(0 to 1);
TMS: in bit;
TDI: in bit;
TCK: in bit;
TDO: out bit;
VDD: linkage bit;
VSS: linkage bit;
NC: linkage bit_vector(0 to 3)
);
use STD_1149_1_2001.all;
attribute COMPONENT_CONFORMANCE of MY_IC : entity is "STD_1149_1_2001";
attribute PIN_MAP of MY_IC : entity is PHYSICAL_PIN_MAP;
constant PLCC20:PIN_MAP_STRING:=
"LED: (1,2,3,4,5,6,7,8), " &
"BTN: (9,10), " &
"TMS: 11, " &
"TDI: 12, " &
"TCK: 13, " &
"TDO: 14, " &
"VDD: 15, " &
"VSS: 16, " &
"NC: (17,18,19,20) ";
attribute TAP_SCAN_MODE of TMS : signal is true;
attribute TAP_SCAN_IN of TDI : signal is true;
attribute TAP_SCAN_CLOCK of TCK : signal is (10.0e3, BOTH);
attribute TAP_SCAN_OUT of TDO : signal is true;
attribute INSTRUCTION_LENGTH of MY_IC : entity is 3;
attribute INSTRUCTION_OPCODE of MY_IC : entity is
"IDCODE (001)," &
"EXTEST (010)," &
"BYPASS (011)," &
"SAMPLE (100) ";
attribute INSTRUCTION_CAPTURE of MY_IC : entity is "00000001";
attribute IDCODE_REGISTER of MY_IC : entity is
"0000" & -- код ревизии или чего-нибудь типа того
"1010101001010101" & -- код модели микросхемы hAA55
"00000000001" & -- код производителя (соответствует AMD)
"1"; -- единица по стандарту IEEE1149.1
attribute REGISTER_ACCESS of MY_IC : entity is
"DEVICE_ID (IDCODE)," &
"BYPASS (BYPASS)," &
"BOUNDARY (EXTEST, SAMPLE)";
attribute BOUNDARY_LENGTH of MY_IC : entity is 10;
attribute BOUNDARY_REGISTER of MY_IC : entity is
"0 (BC_1, LED(0), output2, X)," &
"1 (BC_1, LED(1), output2, X)," &
"2 (BC_1, LED(2), output2, X)," &
"3 (BC_1, LED(3), output2, X)," &
"4 (BC_1, LED(4), output2, X)," &
"5 (BC_1, LED(5), output2, X)," &
"6 (BC_1, LED(6), output2, X)," &
"7 (BC_1, LED(7), output2, X)," &
"8 (BC_1, BTN(0), input, X)," &
"9 (BC_1, BTN(1), input, X)" ;
end MY_IC;
После соединения «DE0-Nano» с отладчиком JTAG, созданием проекта в «TopJTAG Probe» и подключения файла BSDL, мы сможем приблизительно вот так управлять светодиодами и считывать состояния кнопок на демонстрационной плате: