Многие думают, что программировать под Blackfin трудно, но на самом деле это не так. Программировать легко, но трудно потом находить ошибки. Этот документ описывает возможности по отладке процессоров Blackfin® и средств разработки VisualDSP++® (перевод документа EE-307 от компании Analog Devices [1]).
[Подсказки и советы для отладки]
Будут рассмотрены следующие вопросы:
• Отличия запуска приложения в эмуляторе и запуска приложения через загрузку (booting). Особенно это относится к инициализации памяти SDRAM. • Разблокировка ядра B для двухядерных процессоров. Эмулятор делает это автоматически; однако, когда код загружается, ядро B должно быть разблокировано вручную. • Аппаратные ошибки и исключения программы. • Функции и инструменты отладки процессора Blackfin, включая: - Буфер трассировки. - Точки останова (программные, встроенные и аппаратные). - Отладка VDK (VDK Status window и VDK State History window). • Проблемы с отладкой, когда разрешено кэширование. • Прерывания.
Примечание: этот EE-Note не касается проблем, связанных с периферийными устройствами.
Поскольку отладчик JTAG работает довольно медленно, то для ускорения загрузки программы лучше постоянно держать сессию отладчика активной, периодически редактируя код и сразу запуская его компиляцию и отладку клавишей F7. Также старайтесь объемный код, который уже отлажен и проверен (наподобие подпрограмм работы с графикой) поместить в заранее скомпилированные библиотеки *.dlb. Это значительно экономит время на перекомпиляции кода.
[Отличия эмуляции от самостоятельной загрузки]
Программное обеспечение поддержки эмулятора использует файлы .xml для конфигурирования ресурсов, таких как параметры времени SDRAM, к примеру для оценочной платы разработчика EZ-KIT Lite®. Это .xml файлы по умолчанию всех процессоров Blackfin, для которых есть оценочные платформы (т. е. рабочие системы, на которых можно запустить код, например платы EZKIT Lite), где созданы определения для инициализации основных регистров при подключении эмулятора.
Ниже в качестве примера приведен кусок из файла ADSP-BF537-proc.xml, предназначенного для Blackfin-процессора ADSP-BF537:
Этим способом SDRAM будет инициализирована автоматически, когда разрабатывается приложение для платы ADSP-BF537 EZ-KIT Lite, и для запуска приложения использовался эмулятор.
Однако, когда код переносится на самостоятельное приложение (приложение будет загружаться вместо того, чтобы записываться в память процессора эмулятором), пользователь сам должен позаботиться о разрешении контролера SDRAM, если в системе используется SDRAM. Это реализуется подключением файла инициализации на странице Loader диалога Project Options, когда создается файл для загрузчика. Подробности см. в руководстве ADSP-BF533 Blackfin Booting Process (EE-240)[2].
Другое отличие, которое может вызвать проблемы при переносе приложения из сессии эмулятора в самостоятельно загружаемый код, связан с двухядерными системами на Blackfin ADSP-BF561. По умолчанию программное обеспечение "разблокирует" ядро B, и позволяет ему запуститься с начала памяти инструкций L1. Когда должны использоваться оба ядра, ядро B должно быть разблокировано ядром A, что делается очисткой бита 5 в регистре конфигурации сброса системы (system reset configuration register, SICA_SYSCR).
Еще одна проблема, которая иногда возникает как результат разблокирования эмулятором ядра B, связана с изменением рабочих режимов или тактовых частот. В частности ядро B должно быть в состоянии ожидания idle (не просто находиться в точке останова), когда делаются изменения в PLL или регуляторе напряжения. Это может доставить неприятности, например, когда точка останова установлена в ядре B, и код, который меняет частоту PLL, работает на ядре A. Убедитесь в том, что выполняющийся код помещает ядро B в состояние idle до того, как будет изменена частота PLL. Это может быть сделано с помощью дополнительных прерываний или выводов GPIO.
[Ошибки аппаратуры и программные исключения]
Ошибки аппаратуры (hardware errors) и программные исключения (software exceptions) - два специфичных типа событий, которые могут возникать в процессорах Blackfin. Каждое из этих событий имеет отдельную запись в таблице векторов событий (event vector table, EVT). Для каждого из этих событий должны быть установлены обработчики, причем так, чтобы они могли быть перехвачены любым из методов точек останова (которые будут описаны далее в этом документе). В этой точке состояние процессора может быть проанализировано, чтобы понять, в чем была причина отдельного события. В регистре состояния секвенсера (sequencer status register, SEQSTAT) есть 2 поля, которые можно использовать для большего понимания причины недопустимого события. Поле HWERRCAUSE используется для идентификации условия, которое привело к генерации аппаратной ошибки, и поле EXCAUSE используется для идентификации условия, которое сгенерировало исключение.
Аппаратная ошибка может быть сгенерирована разными причинами, такими как доступ к MMR с неправильным размером слова (например, к 16-битному MMR обращаются как к 32-битному, или наоборот), или когда ядро контроллера DMA пытается получить доступ к зарезервированному или не инициализированному пространству в памяти. Адрес RETI будет содержать адрес, близкий к 10 инструкциям, в которых возникла ошибка. Если аппаратные ошибки разрешены и обрабатывается событие, то условие будет очищено, но причина аппаратной ошибки останется от последнего условия ошибки.
Для двухядерных ADSP-BF561 Blackfin аппаратные ошибки генерируется отдельным ядром, будут касаться только этого ядра. Если контроллер DMA сгенерировал аппаратную ошибку, то ошибка будет послана на оба ядра.
Каждый из обработчиков (аппаратной ошибки или исключения) может прочитать поля HWERRCAUSE и EXCAUSE для идентификации причины события. Альтернативно, когда для отладки используется эмулятор, инструкции прерывания могут быть помещены в обработчики, такие как emuxcept, так чтобы процессор остановился всякий раз, когда произошла аппаратная ошибка и/или исключение. Затем в регистре SEQSTAT может быть проанализировано подходящее поле, чтобы определить причину события.
Теперь, когда причина события известна, должен быть помечен адрес незаконной инструкции, чтобы найти проблему в программе. Для исключений регистр возврата из исключения (RETX) содержит адрес "неправильной" инструкции или адрес следующей инструкции, которая должна быть выполнена. Адрес в RETX зависит от типа исключения: service (S) или error (E). В руководстве программирования ADSP-BF53x/BF56x Blackfin Processor Programming Reference [3] перечислены события, которые могут быть причиной исключений, а также из типы (service или error). Для удобства эта таблица приведена в Приложении A.
Для исключений типа E регистр RETX содержит адрес "неправильной" инструкции; для исключений типа S регистр RETX содержит адрес следующей инструкции после "неправильной".
В этом месте неправильная инструкция может быть исследована, чтобы лучше понять проблему. Инструкция обращается к памяти, для которой нет допустимого определения CPLB? Инструкция выполняет load/store из/в место с ошибочным выравниванием? Указатель или индекс указывает на недопустимую область памяти?
Можно установить breakpoint поблизости от инструкции, которая вызвала аппаратную ошибку или исключение, и код может быть отлажен в пошаговом режиме, с отслеживанием регистров адреса (Ix или Px). Установка breakpoint-ов и/или шагание по коду до интересующей инструкции иногда может поменять поведение проблемы (например, проблема при этих условиях может больше не наблюдаться). В таких случаях breakpoint может быть помещен после интересующей инструкции. Обратите внимание, что процессор передает управление в обработчик события, так что breakpoint будет размещен на первую инструкцию обработчика события (обработчик исключения или обработчик аппаратной ошибки).
[Использование буфера трассировки]
16-слотовый буфер трассировки, имеющийся в процессорах Blackfin, позволяет захватить последние не смежные изменения потока выполнения (исключаются аппаратные циклы с нулевыми потерями, zero-overhead hardware loops). Информация в буфере трассировки может способствовать определению причины проблемы или, что более важно, сужению области поиска проблемы таким образом, что может быть получен небольшой тестовый сценарий, позволяющий постоянно наблюдать нежелательное поведение. В предыдущей секции были описаны методы идентификации инструкции, вызвавшей частное событие; однако во многих случаях та же самая инструкция в изоляции не будет показывать проблему. Это то, что происходит перед тем, как инструкция была выбрана и выполнена (в некоторых случаях она даже не добирается до этапа выполнения), это критично на обнулении в первопричине. Например предположим, что инструкция, выполняющая загрузку из памяти, использует регистр P2. Непосредственно перед выполнением этой инструкции будет получено прерывание, которое, по плохой практике программирования, не делает сохранение и восстановление используемых регистров. Код ISR случайно модифицирует регистр указателя P2, и при возврате из ISR будет выполнена оригинальная инструкция загрузки из памяти. Однако P2 больше не указывает на нужное место в памяти, потому что он был перезаписан асинхронным событием, что в результате может послужить источником ранее обсужденных событий. Данные записываются/читаются из неправильной области памяти. Последнее обычно обнаружить труднее.
Буфер трассировки позволяет сохранить изменения потока выполнения, которые произошли перед возникновением проблемы, прежде чем они могли бы быть легко замечены в окне. Запишутся последние 16 пар переходов. Первая запись в паре это источник перехода (например, инструкция вызова), и вторая место назначения (например, первая инструкция вызванной функции). В приведенном ранее примере с P2 первая инструкция данной пары трассировки была бы возвратом из прерывания (RTI), и вторая была бы инструкция загрузки или инструкция перед ней. Так как буфер трассировки кроме инструкций также показывает и адреса неоднородностей потока выполнения, может быть проанализирован адрес инструкции RTI в ISR, чтобы найти, что P2 был модифицирован, и не был восстановлен перед выходом из ISR. Этот ISR мог бы быть частью шедулера RTOS, который использует приложение. Конечно, рассмотренный здесь пример сильно упрощен. Могло бы оказаться, что ISR не реализовал обходное решение известной проблемы.
Вы можете столкнуться с тем, что ничего не будет очевидно (например, со всем этим анализом все еще не будет выяснена причина проблемы). Знание изменений, которые происходили перед возникновением проблемы, может помочь в создании маленького теста, который может быть очень полезен команде службы поддержки, чтобы быстро исследовать проблему и разобраться в ней.
На рисунке 1 показано, как записи структурируются в буфере трассировки. В крайнем левом столбце перечислены циклы от 0 до 31. Циклы 0 и 1 являются последней парой записанных переходов, цикли 2 и 3 предпоследней, и т. д. Второй столбец слева показывает группировку пар. Например, циклы 0 и 1 являются 15-й парой (0xf), циклы 2 и 3 являются 14-й парой (0xe), и циклы 0x1e и 0x1f являются нулевой парой (0x0). Первая инструкция в паре это источник перехода, и вторая место назначения. Для пары 0xf cycle 0 является источником (инструкция RTS), и cycle 1 является инструкцией назначения (CALL Initialize__3VDKFv). Таким образом, эта инструкция была выполнена первой после возвращения из подпрограммы, заканчивающейся на адресе 0xffa086be.
Рис. 1. Пример буфера трассировки.
[Использование точек останова (Breakpoints)]
Эта секция описывает различия между разными видами точек останова: software, embedded и hardware, и объясняет, как их использовать.
Software Breakpoints. Программные точки останова удобны и просты в использовании. Просто сделайте двойной клик на инструкцию в окне редактора кода или окна дизассемблированного кода IDDE, чтобы установить breakpoint, и выполнение приостановится, когда достигнет этой строки кода. Все просто, но тут происходит нечто, что мы не видим - место, куда помещена точка останова, "кэшируется" в эмуляторе. Эмулятор читает память, где находится точка останова, и сохраняет её в своем внутреннем списке точек останова. Когда приложение запускается, он замещает инструкцию в этом месте на инструкцию ловушки. Когда выполнение достигает любой из точек останова, или когда происходит любая приостановка выполнения, инструкция ловушки заменяется обратно на оригинальную инструкцию, которая была "кэширована". Отсюда понятно, что программные точки останова по своей природе деструктивны в том смысле, что временно меняют код. Таким образом, множество встречающихся проблем могут уйти, когда для диагностики используются программные точки останова, потому что время выполнения приложения было изменено из-за природы программных точек останова. На рис. 2 показано, как software breakpoint выглядят в сессии IDDE VisualDSP++.
Рис. 2. Пример Software Breakpoint.
Embedded Breakpoints. Встроенная точка останова является частью самого кода. Она похожа на программную точку останова, за исключением того, что отладчику не нужно просматривать таблицу точек останова или вставлять в приложение "останавливающие" инструкции. Так что этот класс точек останова "как бы не деструктивен". Инструкция emuexcpt приводит к остановке, когда выполняется процессором. Эта инструкция имеет значение только тогда, когда подключен эмулятор; иначе она будет работать как инструкция NOP. Хорошей практикой использовать встроенные точки останова внутри обработчиков событий, потому что они не вносят никаких изменений в приложение, кроме как занимают место в коде, и не влияют на время выполнения приложения, что позволяет уверенно просмотреть состояние процессора, как только произошло событие. При использовании совместно с информацией буфера трассировки, Вы можете исследовать состояние процессора и переходы, которые происходили до того, как событие произошло. На рис. 3 показан пример встроенной точки останова.
EX_INTERRUPT_HANDLER(Timer0_ISR)
{
asm("EMUEXCPT;"); //встраиваемая точка останова// подтверждение обработки прерывания:*pTIMER_STATUS =0x0001;
brkptcounter++; //счетчик аппаратных точек останова// сдвиг старой маски LED:if(sRight_Move_Direction)
{
if((ucActive_LED = ucActive_LED >>1) < =0x0020)
ucActive_LED =0x1000;
}
else
{
if((ucActive_LED = ucActive_LED <<1) ==0x1000)
ucActive_LED =0x0020;
}
// запись нового значения LED в PORTF*pPORTFIO_TOGGLE = ucActive_LED;
}
Рис. 3. Пример Embedded Breakpoint.
Hardware Breakpoints. В отличие от других видов точек останова, аппаратные breakpoint-ы полностью не деструктивны, так как они никак не меняют код приложения. Вместо этого Hardware Breakpoints задействуют аппаратуру логики чипа, которая мониторит и шину данных, и шину инструкций. На процессорах Blackfin аппаратные точки останова реализованы блоком отслеживания состояния регистров (watchpoint register unit). Здесь есть 6 регистров отслеживания инструкций и 2 регистра отслеживания данных. Аппаратные точки останова для инструкции можно установить для 6 адресов инструкций или на 3 диапазонах адресов инструкций. Аппаратные точки останова данных могут быть установлены на 2 определенных адресах данных или на одном диапазоне адресов данных. Аппаратные точки останова могут использоваться либо в памяти типов RAM или ROM.
Чтобы разрешить аппаратные точки останова из IDDE VisualDSP++, перейдите в Settings и выберите Hardware Breakpoints. На рисунке 4 показана одна из страниц Instruction окна Hardware Breakpoints.
Рис. 4. Hardware Breakpoints (Instruction).
Затем может быть указан адрес инструкции или диапазон адресов, чтобы заставить процессор остановиться, когда эти инструкции должны выполниться.
Для доступа к данным может быть указан тип доступа (чтение, запись, или и то, и другое), чтобы сработала остановка эмуляции. На рисунке 5 показана страница Data окна Hardware Breakpoints.
Рис. 5. Hardware Breakpoints (Data).
Затем код можно запустить, и процессор остановится, если было совпадение между внутренним адресом шин инструкции/данных и адресом, указанным в регистрах аппаратных точек останова.
Для аппаратных точек останова есть функция счетчика пропуска, которая может быть использована, чтобы показать, сколько раз игнорировать доступ в указанную область до момента, когда процессор должен быть остановлен. Например, если счетчик пропусков установлен в 0xA, то процессор остановится на десятом совпадении адреса.
[VisualDSP++ Kernel (VDK)]
VDK это ядро реального времени (со странным названием), которое упрощает управление проектами с многими задачами. Однако это добавляет к приложению дополнительный уровень абстракции. По этой причине, как и в любой RTOS, бывает намного определить место ошибки в системе.
VisualDSP++ имеет отладчик, имеющий информацию о ядре, который может показать подробности быстродействия системы, что поможет в настройке приложения и в отладке системы на базе RTOS. Это позволяет Вам визуализировать разные потоки по затрачиваемому времени процессора (например, когда они в состоянии работы, блокирования, готовности и т. д.). Среди других нужд отладки это может быть инструментом для идентификации проблемы, почему какой-то отдельный поток не может запуститься. На рисунке 6 показано окно VDK State History.
Рис. 6. Окно VDK State History (окно истории состояний VDK).
Убедитесь в том, что правильно установлены приоритеты потоков. Вы должны знать потребности каждой задачи в контексте работы в реальном времени. Окно VDK State History может дать инструмент анализа общего распределения баланса времени между потоками.
Есть еще одно полезное окно отладки - VDK Status, отображающее причину ошибки паники ядра (kernel panic error).
Рис. 7. Окно VDK Status.
Пример на рисунке 7 показывает переполнение стека, которое привело к панике ядра. Показанное значение идентифицирует поток (находящийся в состоянии IDLE), для которого размер стека оказался недостаточным.
[Проблемы с динамическими объектами и удалением потоков]
Будьте внимательны с динамическими объектами, которые создаются в потоке (создание экземпляров класса операторами new, выделения блоков памяти и привязка к ним указателей). Если этот поток потому будет уничтожен, и после этого будет создан другой поток, то динамически выделенные объекты с большой вероятностью будут повреждены.
Самый простой способ избежать этой проблемы - создать все потоки при старте приложения VDK и никогда больше не удалять и не создавать потоки. Другой способ - выделять память/создавать объекты статически, либо в том потоке, который никогда не должен быть уничтожен.
[Проблемы, связанные с кэшем]
Когда есть подозрение на проблему с кэшем, сначала проконсультируйтесь с подходящим списком багов процессора (processor anomaly list), чтобы проверить, не подходит ли отдельное наблюдаемое поведение под какие-то характеристики списка.
Если неправильное поведение оказалось не относящимся к известным проблемам процессора, попытайтесь исключить контроллер кэша как причину, переместив интересующую область в память L1. В предыдущих секциях было показано, как определить этот регион. Запустите приложение один раз, когда кэш включена, и второй раз, когда кэш выключена. Проанализируйте, в чем есть отличия в поведении программы. Если проблема все еще наблюдается при выключенном кэше, это может указывать на условие гонки в программном обеспечении. Выключение кэша может изменить время выполнения остальной части приложения, что также может привести к тому, что ошибка перестанет наблюдаться. Поэтому попробуйте перенести интересующий регион памяти в L1 и оставьте кэш включенной. Если проблема осталась, и исключение и/или аппаратная ошибка генерируется в области кода, который находится в L1, то это не связано с проблемой целостности кэша.
Если исключения наблюдаются, обратитесь к ранее приведенной секции статьи, посвященной аппаратным ошибкам и программным исключениям.
Когерентность кэша. Процессоры Blackfin не поддерживают когерентность между памятью кэша и основной памятью. Обычно когерентность может быть проблемой в системах, где канал DMA периферийного устройства обращается к области внешней памяти, для которой задано кэширование. У контроллера кэша нет информации про такие попытки доступа, и как результат контроллер может использовать для вычисления недостоверные (устаревшие) данные, что приведет к непредсказуемым результатам. Программное обеспечение должно гарантировать когерентность путем снятия достоверности строк кэша, к памяти которого было обращение со стороны контроллера DMA.
[Проблемы, связанные с прерываниями]
В коде ISR убедитесь, что операции проталкивания в стек / выборки из стека (push/pop) ресурсов происходят в правильном порядке. Также обратите внимание на отдельную важность проталкивания/выборки RETI. Когда RETI проталкивается в стек, вложенность прерываний разрешается; и соответственно выборка из стека RETI снова запрещает вложенность прерываний [4]. Таким образом, если прерывания с более высоким приоритетом не должны прервать работу кода ISR, то не делайте в этом ISR проталкивание RETI в стек. Если Вы программируете на C/C++, используйте ISR, не позволяющий вложенность:
EX_INTERRUPT_HANDLER(Timer_handler)
Если вложенность должна быть разрешена для частного ISR, используйте следующее определение ISR:
EX_REENTRANT_HANDLER(Timer_handler)
Этот реентрантный обработчик проталкивает в стек RETI в начале своего кода, и выбирает его в конце своего кода (до выполнения инструкции RTI).
Чтобы предотвратить многократный вход в тот же самый ISR, очистите в ISR причину прерывания перед выходом из ISR. Например для таймера ядра очистка бита TINT (timer interrupt) в регистре управления таймером ядра очистит причину прерывания.
Когда используются вложенные прерывания избегайте проблем, которые могут возникнуть из-за использования общих ресурсов. Минимизация времени выполнения ISR позволит низкоприоритетным ISR быть также обработанными в нужное время. Сохранение ISR коротким также уменьшает количество используемых в ISR ресурсов, что облегчает использование стека. При использовании вложенных прерываний может возникнуть другая проблема - переполнение стека. Один из способов детектировать переполнение стека (даже к глубоко вложенных подпрограммах) это чтение указателя стека (SP) в начале ISR, чтобы проверить, находится ли он вблизи конца стека.
[Полезные команды в среде VisualDSP++]
Следующие команды полезны для управления откомпилированным проектом и его отладки:
Команда
Пункт меню
Shortcut
Замечания
Запуск сессии отладки
Session → Connect to target
Осуществляется подключение отладчика (ICE) к процессору DSP через JTAG. После успешного подключения к процессору на плашке окна VisualDSP++ отобразится имя сесии (обычно имя сессии состоит из target, модели процессора и модели эмулятора JTAG, например "ADSP-BF538 via ICE-100B").
Завершение сессии отладки
Session → Disconnect from target
Отключение отладчика от процессора DSP.
Загрузка исполняемого кода (*.dxe)
File → Load Program...
Ctrl + L
Исполняемый код будет загружен в оперативную память (обычно это L1 или L1, L3) процессора DSP. После этого процессор будет остановлен в начале программы (обычно это начало тела функции main).
Запуск кода на выполнение
Debug → Run
F5
Этот пункт меню становится активным после запуска сессии отладки. Загруженный код запустится на непрерывное выполнение (пока не произойдет ислючение ошибки или не встретится точка останова).
Остановка работающей программы
Debug → Halt
Shift + F5
Эти функции (и пункты меню) доступны только после запуска сессии отладки.
Просмотр стека вызовов
View → Debug windows → Call stack
Просмотр дизассемблированного кода
View → Debug windows → Disassembly
Просмотр содержимого памяти процессора
Memory → Blackfin Memory
Call Stack. Это очень полезное информационное окно, помогающее иногда в случае проблем найти ошибочное место в коде. Когда выполнение кода остановлено в отладчике, окно Call Stack показывает текущую историю вызовов подпрограмм по отношению к стеку. Ниже пример скриншота показывает, что сейчас вызвана некая функция sportCallbackFunctionRX, что произошло после обработчика DMACallback (судя по названию, это функции обратного вызова, обычно применяемые в стандартных библиотеках VDK и System Services ADI). В самом низу списка виден самый первый вызов подпрограммы: функция VDK::Thread::RunFunc.
Окно Call Stack показывает текущий снимок состояния программы. При наличии сложного приложения наподобие проекта VDK, контекст выполнения может быть переключен ядром или по прерыванию, так что мы можем увидеть в этом окне совершенно другую картину даже через несколько микросекунд. Ниже в качестве примера показан еще один скриншот окна Call Stack того же самого приложения, однако здесь мы видим остановку в функции потока ожидания, вызов которой периодически осуществляется ядром VDK.
Disassembly. Окно дизассемблера показывает ассемблерный код, который сгенерировал компилятор (обычно это компилятор C/C++). Когда программа загружена, но еще не запущена, то мы увидим окно дизассемблера наподобие следующего:
Больше всего полезной информаци в окне дизассемблера будет при отдадке проекта, скомпилированного в конфигурации Debug (мы увидим имена функций и переменных). Информация окна дизассемблера проекта, скомпилированного в Release, будет существенно беднее.
Memory. Чтобы проинспектировать содержимое области памяти процессора, выполните следующие шаги:
1. Загрузите и запустите скомпилированное приложение в среде VisualDSP++. 2. Нажмите комбинацию клавиш Shift+F5 для остановки программы. 3. Выберите в меню Memory -> BLACKFIN Memory. 4. Введите в пустом поле выпадающего списка абсолютный шестнадцатеричный адрес начала области памяти, которую хотите просмотреть, или метку, соответствующую символическому имени переменной, функции или массива (например, введите имя переменной __Processor_cycles_per_sec). 5. Нажмите клавишу Enter, и Вы увидите в окне отображение содержимого указанного участка памяти. 6. Контекстное меню окна BLACKFIN Memory (доступное правым кликом мыши) позволяет выбрать один из возможных форматов отображения содержимого памяти (через пункт Select Format).
Statistical Profiling. Статистический профилировщик позволяет выявить высоконагруженные участки кода, т. е. в какой части программы процессор больше всего тратит своего времени. Это облегчает алгоритмическую оптимизацию кода, и позволяет добиться эффективного и быстро работающего кода (подробнее см. [7]).
[Общие выводы]
Этот EE-Note описывает инструментарий VDK и функции процессора Blackfin, которые доступны для помощи в поиске решения проблем.
Прежде всего проверяйте список ошибок для Вашей ревизии процессора (anomaly list), чтобы проверить, что подобная проблема уже известна. Если это так, что реализуйте предоставленное обходное решение проблемы. Чтобы автоматизировать программную поддержку известных silicon errata, убедитесь, что пользуетесь последними версиями инструментария, и разрешены методы обхода проблем кремния (silicon workarounds).
Приложения должны устанавливать обработчики событий (обработчики исключений, обработчики прерываний) до запуска основного приложения, чтобы Вы могли перехватывать необходимые события.
Проверьте, как работает система. Что точно не работает должным образом? Генерируются исключения/аппаратные ошибки? Если это так, то что за исключение и/или аппаратная ошибка? Таблицы в приложении A помогут определить это. Наблюдаются ли на периферийных устройствах переполнение/опустошение буфера? Генерируются ли ошибки DMA?
Найдите способ увеличить вероятность повторения ошибки. Хотя это не всегда получается, увеличение частоты возникновения проблемы увеличивают шансы для исправления проблемы. К увеличению повторяемости может привести увеличение или сокращение количества итераций цикла, изменение напряжения питания ядра, подстройка частот ядра и/или системы, и т. д. Следует заметить, что нужно делать только одно изменение за один раз. Если модифицированная переменная не влияет на ошибку, оставьте прежнее значение переменной и сделайте какое-то другое новое изменение.
Используйте программные точки останова для исследования состояния процессора до срабатывания ошибки. Если сбой срабатывает, когда установлены программные точки останова, то попробуйте использовать встроенные точки останова, или, как самое жесткое средство, аппаратные точки останова.
Если генерируются аппаратные ошибки/исключения, найдите соответствующие причины из регистра состояния секвенсера, и проверьте таблицы в приложении A, чтобы увидеть, что могло сгенерировать такие события.
Перехватывайте исключения в соответствующих обработчиках исключения, используя встраиваемые или аппаратные точки останова.
Используйте окно трассировки (Trace window), чтобы исследовать переходы процессора до момента возникновения проблемы.
Сохраните все регистры для последующего анализа, что можно в VisualDSP++ сделать выбором меню Register->Save Registers, как это показано на рисунке 8.
Рис. 8. Функция сохранения регистров.
Если после всех вышеописанных шагов исправить ошибку не получается, то зная последовательность событий, которые приводят к нежелательному поведению, Вы сможете создать маленькое тестовое приложение для воспроизведения ошибки. Как только такой тестовый кейс создан, сделайте обобщение найденной Вами информации для службы поддержки, и вышлите эту информацию вместе с тестовым приложением. Это позволит быстро воспроизвести проблему, что очень поможет в её решении.
[Отладка Release]
Часто встречаются ситуации, когда проект работает в Debug, а в Release нет. Это сигнализирует о явной ошибке. Однако несмотря на то, что Вы можете в отладчике запустить проект, скомпилированный в Release, возможности отладки такой конфигурации существенно ограничены. Нельзя ставить точки останова в исходном коде (можно ставить только в окне Disassembler), затруднен (хотя возможен, о чем далее) просмотр содержимого переменных по их именам и т. д. Что можно сделать в таком случае? Ниже перечислены несколько полезных советов по отладке в такой ситуации.
Светодиод. Это самый простой и быстрый метод диагностики, который можно использовать для анализа поведения программы. Можно измерять (с помощью осциллографа) время выполнения отдельных участков интересующего кода, анализировать проход процессора по разным веткам кода программы.
Организация лога. Можно сделать текстовый лог, в который выводятся диагностические сообщения. ИМХО идеальный лог - вывод диагностики в консоль терминала через порт UART. Подробно способы ввода/вывода описаны в [5].
Имейте в виду, что вывод по умолчанию (printf), настроенный в на отображение сообщений в окне Console VisualDSP++, работает очень медленно, поэтому для отладки очень неудобен, поскольку влияет на поведение программы в реальном времени.
Как вариант можно сделать запись лога в память. Способ организации такого лога зависит от особенностей реализации системы и наличия свободной памяти.
Просмотр значений глобальных переменных. Для того, чтобы можно было просмотреть в отладчике значение глобальных переменных проекта Release, нужно сначала узнать их абсолютные адреса. Тогда можно найти эти переменные на карте памяти процессора, и просмотреть их значения в окнах Blackfin MEMORY, Disassembly и даже в окне Expressions (конечно, нужно остановить в отладчике выполнение программы, нажав Shift-F5).
Адрес нужной переменной можно узнать по её имени несколькими способами. Во-первых, переменную можно найти по имени, если поставить впереди символ подчеркивания (demangle-имя). Ниже показан скриншот, где переменная inSB найдена по её имени _inSB.
Во-вторых, можно найти переменную в map-файле, который генерируется в процессе линковки (если поставить галочку "Generate symbol map" в свойствах проекта, см. Project Options -> Link -> General -> Additional Output), и оттуда узнать её абсолютный адрес. После этого можно найти и просмотреть значение переменной по его адресу в окне Expressions (см. скриншот ниже).
Поиск нужного кода в дампе Disassembly. Аналогичными способами, как находят глобальные переменные, можно найти код в окне Disassembly - через demangled имена функций и с помощью map-файла. Тогда можно ставить точку останова прямо в дамп Disassembly. Ниже на скриншотах в качестве примера показан процесс поиска кода функции по имени в map-файле.
Деление кода на части. При необходимости исследуемую часть кода, которая подозревается на ошибку, можно скомпилировать без оптимизации, а весь остальной проект с оптимизацией, в конфигурации Release. Тогда в участке кода, который скомпилирован без оптимизации, можно ставить точки останова как обычно, и как обычно просматривать содержимое переменных.
Для управления оптимизацией с помощью директивы #pragma optimize, с помощью неё можно отключать оптимизацию для отдельных функций (см. [8]). Иногда удобнее перенести исследуемый код в отдельный модуль, и скомпилировать его отдельно без оптимизации в библиотеку.
См. секцию руководства [1], посвященную правилам множественных ошибок (multi-issue rules).
Data access CPLB protection violation
0x23
E
Сделана попытка прочитать или записать ресурс, доступный только для режима супервизора, или был недопустимый доступ к памяти. Ресурсами супервизора являются регистры и инструкции, которые зарезервированы для использования супервизором: регистры супервизора, все MMR и инструкции, относящиеся только к супервизору (одновременный, двойной доступ к двум MMR с использованием DAG-ов генерирует такой тип исключения). Дополнительно эта запись используется для сигнализации о нарушении защиты, вызванном неразрешенным доступом к памяти, и это определено блоком Memory Management Unit (MMU) защищенного кэшируемого буфера (cacheability protection lookaside buffer, CPLB).
Data access misaligned address violation
0x24
E
Сделана попытка не выровненного по адресу доступа к памяти данных или данным кэша.
Unrecoverable event
0x25
E
Невосстановимое событие. Например, исключение было сгенерировано при обработке предыдущего исключения.
Data access CPLB miss
0x26
E
Используется блоком MMU для сигнализации о промахе CPLB при доступе к данным.
Data access multiple CPLB hits
0x27
E
Для выборки адреса данных есть больше одной совпадающей записи CPLB.
Watchpoint match
0x28
E
Исключение, вызванное совпадением точки останова отладки. При этом установлен один из битов EMUSW в регистре управления установки контрольной точки (Watchpoint Instruction Address Control register, WPIACTL).
Instruction fetch misaligned address violation
0x2A
E
Сделана попытка не выровненной выборки кэша инструкции. При таком виде исключения адрес возврата будет предоставлен в регистре RETX, и именно этот адрес выровнен ошибочно, а не адрес нарушающей инструкции. Например, если произошло косвенное ветвление на ошибочно выровненный адрес в регистре P0, то адрес возврата в RETX будет равен P0, а не адресу инструкции ветвления. (Обратите внимание, что это исключение никогда не будет сгенерировано относительными ветвлениями от PC, оно будет действовать только для косвенного ветвления.) Для процессора ADSP-BF535 есть специальное поведение.
Instruction fetch CPLB protection violation
0x2B
E
Недопустимая выборка инструкции доступа (memory protection violation, нарушение защиты памяти).
Instruction fetch CPLB miss
0x2C
E
Промах CPLB на выборке инструкции.
Instruction fetch multiple CPLB hits
0x2D
E
Адресу выборки инструкции соответствует больше одной совпадающей записи CPLB.
Illegal use of supervisor resource
0x2E
E
Сделана попытка использования регистра супервизора или его инструкции из режима пользователя (User mode). Ресурсы и регистры супервизора зарезервированы только для использования в режиме супервизора. Это регистры супервизора, все MMR, инструкции, предназначенные только для супервизора.
Примечание 1: E означает ошибку (Error), S означает службу (Service).
Таблица 2. Аппаратные условия, которые приводят к прерываниям аппаратной ошибки.
Аппаратное условие
HWERRCAUSE (hex)
Примечания/примеры
System MMR Error
0x02
Ошибка может произойти, если происходит доступ к недопустимому системному MMR, если к 32-разрядному регистру происходит обращение из 16-разрядной инструкции, или если к 16-разрядному регистру осуществляется доступ из 32-разрядной инструкции.
External Memory Addressing Error
0x03
(Не применимо к BF535) была сделана попытка резервирования не инициализированной памяти.
Performance Monitor Overflow
0x12
Переполнения монитора быстродействия. Подробнее см. раздел "Performance Monitor Unit" руководства [1].
RAISE 5 instruction
0x18
Программа выдала инструкцию RAISE 5 для вызова Hardware Error Interrupt (IVHW).