======Немного о Jtag, ISC и 5576РС1У======
Подвернулась небольшая подработка по программированию микросхемы конфигурирования ПЛИС 5576РС1У. Здесь собраны тезисы того, что удалось узнать при решении данной задачи.
=====Про 5576РС1У =====
Микросхема 5576РС1У - это микросхема флеш памяти, которая выдает сохраненные в ней данные через последовательный интерфейс в микросхему ПЛИС, тем самым ее конфигурируя. Диаграммы режима PassivSerial уже были представлены тут - [[https://startmilandr.ru/doku.php/doc:mk:5576rs1u|Конфигурация в режиме Passive Serial]]
5576РС1У является аналогом микросхемы EPC4QC100N от Altera, поэтому алгоритм работы с 5576РС1У можно найти в BSDL файле для EPC4QC100N ([[https://github.com/StartMilandr/MDR_Pack_v6/blob/master/PACK_Gen/Files/Examples/All_Boards/5576RC1U_Test/epc4q100_1532.bsd|epc4q100_1532.bsd]]) и в векторном файле [[https://github.com/StartMilandr/MDR_Pack_v6/blob/master/PACK_Gen/Files/Examples/All_Boards/5576RC1U_Test/test_epc.svf|test_epc.svf]].
* BSDL файл - это файл описания внутренних регистров микросхемы для реализации периферийного сканирования (Boundary Scan Description Language).
* SVF файл - это файл описания воздействий на микросхему, которым проверяется ее функционал (Serial Vector Format ).
Доступ к внутренним регистрам микросхемы и подача тестовых воздействий происходят по интерфейсу JTAG. Под него эти файлы и написаны.
====BSDL файл====
В BSDL файле нам интересен в первую очередь раздел с описанием регистров и инструкций. Вот содержимое раздела:
-- *********************************************************************
-- * INSTRUCTIONS AND REGISTER ACCESS *
-- *********************************************************************
attribute INSTRUCTION_LENGTH of EPC4Q100 : entity is 10;
attribute INSTRUCTION_OPCODE of EPC4Q100 : entity is
"BYPASS (1111111111), "&
"EXTEST (0000000000), "&
"SAMPLE (0001010101), "&
"IDCODE (0001011001), "&
"USERCODE (0001111001), "&
-- Following 10 instructions are IEEE 1532 instructions
"ISC_ENABLE (0001000100), "&
"ISC_DISABLE (0001001010), "&
"ISC_PROGRAM (0110010110), "&
"ISC_ERASE (0110010010), "&
"ISC_ADDRESS_SHIFT (0100001110), "&
"ISC_READ_INFO (0001000010), "&
"ISC_READ (0110100110), "&
"ISC_NOOP (0011111111), "&
"ISC_STAT (0000111110)";
По данному отрывку мы узнаем перечень доступных инструкций и то, что все они являются 10-битными. Инструкции с префиксом **ISC** относятся к интерфейсу **In-System Configuring**, который регламентируется стандартом **IEEE 1532**. Который является в свою очередь расширением стандарта JTAG (IEEE 1149.1 Standard Test Access Port and Boundary-Scan Architecture - [[https://ru.wikipedia.org/wiki/JTAG|Wikipedia]]).
Ранее каждый производитель предлагал свои способы прошить то или иное устройство, что затрудняло работу с микросхемами разных производителей (например в составе одного устройства). Требовалось изучение каждого отдельного способа программирования и необходим был свой программатор. Чтобы это дело упростить придумали отдельный стандарт на внутрисхемное конфигурирование (ISC) и теперь каждый производитель внутри микросхемы реализует поддержку команд данного стандарта.
Найти в сети описание стандарта ISC (IEEE 1532) не удалось, поэтому остается только предположить назначение инструкций исходя из их названия. Некоторые уточнения:
* IDCODE - Позволяет считать идентификатор микросхемы, в данном случае это 0x0100A0DD.
* USERCODE - Позволяет записать / считать ID который можно использовать в своих целях. Например версия прошивки.
* ISC_ENABLE / ISC_DISABLE - вход в режим ISC и выход.
* ISC_NOOP - вероятно No Operation.
* ISC_STAT - статус исполнения инструкции.
* ISC_ADDRESS_SHIFT - установка текущего адреса перед ISC_READ / ISC_PROGRAM / ISC_ERASE.
* ISC_PROGRAM / ISC_READ - вход в режимы программирования / чтения. Адрес инкрементируется автоматически с каждым новым словом данных
* ISC_ERASE - стирание сектора данных. Стертые данные соответствуют логическим 1-цам, соответственно при программировании записываются только нули. Стирается сектор целиком.
Далее в файле представлены доступные регистры:
attribute REGISTER_ACCESS of EPC4Q100 : entity is
"DEVICE_ID (IDCODE), "&
-- Following 7 registers are IEEE 1532 registers
"ISC_Default[2] (ISC_DISABLE, ISC_NOOP)," &
"ISC_PData[16] (ISC_PROGRAM)," &
"ISC_RData[18] (ISC_READ, ISC_STAT)," &
"ISC_Sector[23] (ISC_ERASE)," &
"ISC_Address[23] (ISC_ADDRESS_SHIFT)," &
"ISC_Config[2] (ISC_ENABLE)," &
"ISC_Info[5] (ISC_READ_INFO)";
По этому описанию мы узнаем разрядность внутренних регистров. Чтобы записать или считать тот или иной регистр необходимо сначала подать инструкцию, а затем считать или записать значение в данный регистр. Грубо говоря, записывая по jtag инструкцию мы выбираем внутренний регистр с которым дальше будем обмениваться данными.
Основные интересующие нас регистры:
* ISC_PData - 16-битный регистр в который записываются слова для записи в память. Слова пишутся по одному, необходимо выждать лишь требуемую задержку.
* ISC_RData - 18-битный регистр чтения значений из памяти. Используется для верификации данных, которые были запрограммированы. Два младших бита являются значениями CRC для 16-битного слова.
* ISC_Sector - 23-битный адрес сектора, который будет стерт.
* ISC_Address - 23-битный адрес с которого будут читаться слова или куда слова будут программироваться.
В этом же файле BDSL описано как применять эти инструкции и регистры. Возьмем к примеру функцию программирования памяти:
"FLOW_PROGRAM (array) " &
"INITIALIZE " &
"(ISC_ADDRESS_SHIFT 23:000000 WAIT TCK 1)" &
"(ISC_PROGRAM 16:? WAIT 3.0e-4 )" &
"REPEAT 249999 " &
"(ISC_PROGRAM 16:? WAIT 3.0e-4 )";
Я трактую это следующим образом:
* **"ISC_ADDRESS_SHIFT 23:000000"** - Подать инструкцию ISC_ADDRESS_SHIFT и 23-битный адрес равный 0x000000.
* **"WAIT TCK 1"** - После этого следует подать один тактовый сигнал на вывод TCK интерфейса JTAG.
* **"ISC_PROGRAM 16:?"** - Записать инструкцию ISC_PROGRAM и 16-ное значение данных. (? - означает не конкретное значение как в случае с адресом, а значение из массива array).
* **"WAIT 3.0e-4"** - выждать задержку 300мкс пока слова программируется.
* **"REPEAT 249999"** - повторить все это еще 249999 раз.
*
В обрывках литературы который выдал поиск в Google встречается различие между задержками //"WAIT TCK 1"// и //"WAIT 3.0e-4//". Суть его в том, что когда указано //"WAIT TCK 1"//, то это требование именно подать тактовый сигнал. Т.е. если бы было написано //"WAIT TCK 100"//, то необходимо было бы подать 100 тактовых периодов на вывод TCK интерфейса JTAG. В отличие от этого, задержка //"WAIT 3.0e-4//" - это всего лишь временной интервал.
В машине состояний JTAG, которую мы рассмотрим дальше, наличие лишнего такта TCK не имеет никакого смысла. Возможно требование к TCK это нечто из расширения ISC, поскольку большинство инструкций ISC в BSDL файле заканчивается так. Поэтому при реализации обмена мы этот такт оставим.
Аналогично расшифровывается чтение памяти, например верификация того, что память стерта:
"FLOW_BLANK_CHECK " &
"INITIALIZE" &
"(ISC_READ WAIT TCK 1 16:DFFF*0000, 2:2*0) "&
"(ISC_ADDRESS_SHIFT 23:000000 WAIT TCK 1)" &
"(ISC_READ WAIT TCK 1 18:3FFFE*FFFFF)" &
"REPEAT 249999 " &
"(ISC_READ WAIT TCK 1 18:3FFFE*FFFFF)," &
* **"ISC_READ WAIT TCK 1"** - Подается команда с лишним тактом
* **16:DFFF*0000, 2:2*0** - Читается 16-битный ответ и еще 2 бита CRC. Используется маска //AND 0000//, что говорит о том, что чтение служебное (пустое) - данные никак не проверяются.
* **"ISC_ADDRESS_SHIFT 23:000000 WAIT TCK 1"** - Выставляется начальный адрес.
* **"ISC_READ WAIT TCK 1"** - Запись инструкции чтения.
* **18:3FFFE*FFFFF** - Читается 18-битное слово памяти, которое должно быть равно 3FFFE (стертое значение 0xFFFF с CRC). Маска на считанное значение FFFFF.
* **"REPEAT 249999"** - Повторить 249999 раз.
Остальные функции можно посмотреть в самом BSDL файле. Под описанием функций представлены операции, которые выполняются с микросхемой программатором. Каждая операция состоит из нескольких функций. Мы рассмотрели сейчас только функции PROC_PROGRAM(array) и PROC_BLANK_CHECK:
attribute ISC_ACTION of EPC4Q100 : entity is
"VERIFY_IDCODE = (TEST_VERIFY_IDCODE)," &
"PROGRAM = (TEST_VERIFY_IDCODE RECOMMENDED," &
"PROC_ENABLE," &
"PROC_ERASE," &
"PROC_BLANK_CHECK," &
"PROC_PROGRAM(array)," &
"PROC_VERIFY(array)," &
"PROC_PROGRAM_DONE," &
"PROC_VERIFY(donebit)," &
"PROC_DISABLE)," &
"VERIFY = (TEST_VERIFY_IDCODE RECOMMENDED," &
"PROC_ENABLE," &
"PROC_VERIFY(array)," &
"PROC_VERIFY(donebit)," &
"PROC_DISABLE)," &
"ERASE = (TEST_VERIFY_IDCODE RECOMMENDED," &
"PROC_ENABLE," &
"PROC_ERASE," &
"PROC_BLANK_CHECK," &
"PROC_DISABLE)," &
"BLANK_CHECK = (TEST_VERIFY_IDCODE RECOMMENDED," &
"PROC_ENABLE," &
"PROC_BLANK_CHECK," &
"PROC_DISABLE)," &
"VERIFY_DONEBIT = (TEST_VERIFY_IDCODE RECOMMENDED," &
"PROC_ENABLE," &
"PROC_VERIFY(donebit)," &
"PROC_DISABLE)," &
"PROGRAM_DONE = (PROC_ENABLE," &
"PROC_PROGRAM_DONE," &
"PROC_DISABLE)";
====Интерфейс JTAG====
Из BSDL файла мы узнали что в 5576РС1У есть внутренние регистры и для доступа к ним есть набор инструкций. Встает вопрос - что с этим делать, куда и как это все подавать? Для этого необходимо рассмотреть машину состояний JTAG и понять как она работает.
В общем-то моя многолетняя фобия перед JTAG оказалась напрасной. Это достаточно простой последовательный интерфейс для записи инструкций и значений во внутренние регистры. Что там дальше делать с этими инструкциями и регистрами решает внутреннее устройство микросхемы. Назначение JTAG осуществить транзакцию, к дальнейшему он уже отношения не имеет.
Основным регистром является регистр BoudaryScan который бывает длиной в сотни бит и позволяет тестировать выводы микросхемы в готовом устройстве. Есть так-же регистр ByPass длиной в один бит, который позволяет пропускать транзакции сквозь микросхему в следующие микросхемы объединенные в одну Jtag цепь (DaisyChain). Бывают всякие специализированные регистры. Например, как мы уже знаем, в 5576РС1У есть регистры ISC через которые происходит программирование микросхемы.
**Линии JTAG:**
* TCK - тактовый сигнал, весь "экшн" происходит по возрастающему фронту этого сигнала.
* TDI - по фронту TCK уровень на данном выводе записывается в текущий сдвиговый регистр внутри микросхемы.
* TDO - одновременно с защелкиванием бита TDI в сдвиговый регистр, из этого регистра выдвигается бит на вывод TDO.
* TMS - сигнал перемещения по машине состояний JTAG.
* TRST - опциональный сигнал сброса подключенного устройства, в микроконтроллерах обычно подключается к выводу Reset.
{{doc:doclist:jtagregupdate.png}}
Про TDI и TDO полагаю понятно, что они подключаются с двух сторон внутреннего сдвигового регистра. Данные от TDI вдвигаются в регистр, а из регистра синхронно данные выдвигаются в линию TDO. Но поскольку регистров внутри устройства может быть несколько, то необходим способ выбора того регистра с которым будут работать линии TDI и TDO. Для этого предназначен автомат состояний и перемещение по этому автомату происходит с помощью сигнала TMS. Сразу стоит сказать, что TDI и TDO подключаются к внутреннему регистру только в состояниях **Shift-DR** и **Shift-IR**. Поэтому чтобы записать / считать регистры необходимо в автомате состояний дошагать до Shift-DR или ShiftIR соответственно.
**IR** - это регистр инструкций. Он один и он выбирает который внутренний регистр будет подключен к линиям TDI и TDO в состоянии Shift-DR. Например записав в IR инструкцию ISC_ADDRESS_SHIFT, а в DR записав значение адреса мы переключим в 5576РС1У текущий активный адрес. Посмотрим как это осуществить по машине состояний JTAG.
{{doc:doclist:jtagstates.png}}
//(Картинка из шикарного видео, где наглядно расказывается про Jtag - [[https://www.youtube.com/watch?v=PhaqHKyAvR4|YouTube - TechSharpen, "JTAG TAP Controller Tutorial"]])//
При работе по JTAG обычно выбирают "парковочное" состояние - это то состояние в которое возвращается автомат между транзакциями. Обычно это либо состояние Idle, либо Select-DR. Select-DR подходит, потому что из него можно попасть и в ветвь DR и в ветвь IR. Но состояние Idle лучше тем, что в нем можно подать импульс TCK при TMS=0, который ничего не изменит, т.к. автомат останется в том-же Idle. А нам как раз необходим один лишний такт TCK для реализации выражения //"WAIT TCK 1"// из файла BSDL. Состояние Reset не подходит под парковочное, потому что при выходе в reset к линиям TDI и TDO подключается регистр по умолчанию, а это обычно ByPass или BoundaryScan.
Выставление внутреннего адреса происходит в два этапа - запись инструкции ISC_ADDRESS_SHIFT в IR и запись самого адреса через регистр DR. Запись инструкции по автомату состояний происходит так:
//допустим мы находимся в состоянии idle, тогда подаем
TMS Idle
1: -> Select-DR
1: -> Select-IR // Дошли до ветки инструкций
0: -> Caprute-IR
0: -> Shift-IR // Перешли в состояние сдвига бит IR
0: TDI[0] -> reg ->TD0[0] // Начинаем побитно сдвигать инструкцию в регистр IR
0: ... // ISC_ADDRESS_SHIFT (0100001110), 10 bit
0: TDI[9] -> reg ->TDO[9] // На TDO выдвигаются биты предыдущего значения IR
1: -> Exit-IR
1: -> Update-IR // Значение сдвигового регистра записалось в регистр инструкций.
// Теперь к линиям TDI и TDO подключен регистр ISC_Address[23]
0: -> Idle // Возвращаемся в Idle
0: -> Idle // В Idle делаем дополнительный такт TCK - "WAIT TCK 1"
two beer...
**Диаграмма программирования слова** (кликабельно):
{{doc:jtag:flowprogram.png}}
* Красными линиями на фронтах TCK выделены служебные такты на перемещение по машине состояний JTAG.
* Синими линиями выделены биты данных задвигаемые через линию TDI во внутренние регистры.
* Сдвиг последнего бита совмещен с переходом на состояние Exit1, поэтому линия покрашена разноцветно.
* Фиолетовым цветом выделен служебный такт "WAIT TCK 1".
====Диаграмма чтения IDCODE из 1986ВЕ1Т====
{{doc:jtag:jtag_ve1_idcode.png}}
====SVF файл====