Нам представилась возможность провести еще одно небольшое, но крайне поучительное тактическое занятие
Тематику этого поста навеяла рассылка от Шерлока Омса — периодически там размещаются истории о нетривиальных инженерных задачах, возникших при диагностировании различных электронных устройств. Вот и подумалось, а почему бы и нет? Хотя прекрасно понимаю, что тематика достаточно специфическая, требует вполне конкретных узкоспециализированных знаний и вряд ли будет интересна широкому кругу читателей, но нескольно приятных минут узкому кругу ценителей аппаратных загадок способна доставить. Итак для тех, кто знает, что такое шина данных и как она устроена — история, в которой будут и корабли, и башмаки, и сургуч, и капустные пальмы.
В процессе проектирования устройства на МК 1986ВЕ1Т, о котором я уже писал, возникла необходимость взаимодействовать с внешними микросхемами FLASH памяти через достаточно быстрый интерфейс, желательно параллельный. К счастью, в рассматриваемом МК такая возвожность присутствует, и для организации доступа к устройствам, отображенным на память, но не включенным в состав собственно МК, можно использовать полную (32 разряда адреса, 32 разряда данных, 2 управляющих сигнала, 4 сигнала сопровождения) внешнюю шину, причем обмен для пользовательской программы выглядит абсолютно прозрачным.Как всегда, благодарности разработчикам за включение такой опции, и, как всегда, выражения неудовольствия по поводу явно недостаточной документации, хотя пост и не об этом. В силу ряда особенностей разработки и контроллера, использовалась не вся ширина шины данных, а только 8 разрядов, начиная с 03го, что абсолютно несущественно для описания обнаруженной проблеммы. Мк вместе со всеми выходными буферами питается от +3.3, кроме того, для обеспечения работы других устройств на шину данных были подключены подтягивающие резисторы 2 ком к напряжению +5. После сборки опытного экземпляра устройства началась отладка и первые тестовые примеры (естественно, или, как еще говорят, разумеется) не заработали, после чего взяли (здесь и далее используется множественное число, поскольку мы проводили эту работу с молодым коллегой, который почему-то категорически не желает писать посты на Хабре) осциллограф и полезли смотреть времянки. И вот тут то и было обнаружено интересное явление. Ожидаемая осцилограмма сигналов на шине данных должна выглядеть следующим образом (красный и зеленый цвета — это не я придумал, просто так получилось при заливании на Хабр):
Фрагмент осциллограммы, помеченой цифрой 2 — это ожидаемое поведение шины при отсутствии адресованного внешнего устройства (достигается переводом входа выборки в неактивное состояние). МК снимает данные с шины (черная линия на верхней диаграмме) и напряжение на ней начинает подтягиваться к питанию через подпитывающий резистор. Через некоторое время МК выдает активный уровень (нулевой) сигнала чтения (зеленый сигнал на нижней диаграмме) и в этот момент внешнее устройство должно передать данные (поскольку оно неактивно, продолжается подтяжка), затем по истечении определенного времени активный уровень сигнала чтения снимается, внешнее устройство освобождает шину, дальнейшее состояние на шине неопределенно, в нашем случае продолжается подтяжка. Все логично и понятно, но дело в том, что первоначально были обнаружены диаграммы, показаные на участке 1. В этом случае перед подачей сигнала чтения МК выдавал на шину высокий уровень и продолжал его удерживать на протяжении времени чтения и даже дальше, лишь по истечению значительного времени (порядка миллисекунд) шина данных переходила в отключенное состояние. Несколько неожиданно, но сначала я отнесся к ситуации без должного внимания — решил, что где-то ошибка в настройках пинов и надо ее искать (поскольку программу писал молодой коллега, мне было легко предположить наличие в ней возможных ошибок, вот если бы ее писал я, то тут ситуация не была бы столь однозначой :) ). Твердая уверенность в наличии ошибки настроек исчезла после того, как быо установленно, что ВСЕ 16 линий данных (из 32) настроены одинаково, а сбои имеются только на 4 из них, причем это биты 4,5, 8 и 11.
Думаем дальше и экспериментируем. Появляется идея, что нельзя проводить чтение сразу после записи (это в докуметации не отражено, но при работе с Миландром мы уже привыкли что-то домысливать), поэтому делаем 2 последовательных чтения, в надежде что второе пройдет правильно.
data=*buffaddr;
data=*buffaddr;
И вот тут начинается самое интересное — второе чтение действительно проходит правильно, НО первое тоже становится правильным — очень интересное явление — совершенно не могу себе представить его механизм — то есть не могу представить разумный механизм влияния последующей команды на предыдущую. Беглый просмотр порожденного ассемблерного кода дает подсказку — изменился адрес расположения команды первого чтения в силу особенностей работы линкера — уже лучше, механизм влияния адреса на выполнение команды придумать проще. Для того, чтобы поисследовать поведение МК, выделяем фрагмент, относящийся к обмену с внешней шиной, из общей программы, путем удаления всего ненужного. И получаем очередной сюрприз — неправильное чтение не наблюдается даже при одинарном обращении, хотя адрес команды остается неизменным. Путем вставления удаленных фрагментов обратно выясняем, что при подключении функции вычисления CRC16 неправильное чтение наболюдается, а при ее отсутствии — нет, причем эта функция совершенно очевидно никак с внешней шиной не взаимодействует и влиять на чтение разумными способами не может. Дальнейшие эксперименты показали, что важна не функция подсчета CRC16 как таковая, а наличие в ней блока данных промежуточных сумм, более того, размер этого блока, то есть при коде:
static CRC16Buff[256]; ошибка наблюдается а при
static CRC16Buff[215]; (и менее 215) - ошибка обращения отсутствует
Каким образом может этот фрагмент влиять на код исполняемый совсем в другом месте? Обнаруживаем, что единственное изменение — в значении стека, поскольку изменилось требуемое место под глобальные переменные. То есть получается, что неправильное обращение происходит при выполнении команды с определенных мест при определенных значениях стека, причем количество ошибочных битов в слове невелико? Самое время вспомнить первое правило инженера — «Чудес на свете не бывает». Можно предположить, что это остаток какой то отладочной функции VHDL, который сигнализировал об определенных ситуациях и не был убран из релиза. Выглядит как мысль сильно обкурившегося разработчика, но пока другой гипотезы нет, поскольку божественное вмешательство мы отвергаем. Другая мысль — «вот ты какой, северный олень» — мы обнаружили ЗАКЛАДКУ, правда, довольно-таки бессмысленную, но кто их, ребят из АНБ, может понять.
Продолжаем исследования и с изумлением обнаруживаем, что перемещение команды по различным адресам (путем добавления NOP) ни к чему не приводит — ошибка не появляется, или, соответственно не исчезает для разных значений стека, то есть гипотезу с адресом следует отвергнуть. Но как же тогда добавление второй команды влияет на первую? Смотрим на ассеблерный код внимательнее и обнаруживаем еще изменения, а именно при одинарном чтении компилятор порождает
mov r0, sp
ldrh r1,[r4]
strh r1,[r0]
А при двух чтениях подряд он провел отпимизацию:
mov r2, sp
ldrh r1,[r4]
strh r1,{r2]
ldrh r1,[r4]
strh r1,[r2]
В это трудно было поверить, но действительно далее устанавливаем, что неправильное чтение имеет место тогда, и только тогда, когда в регистре r0 лежит вполне конкретное значение, причем неважно, будет ли этот регистр использоваться в дальнейшем. По сравнению с предыдущей совершенно сумасшедшей гипотезой о связи указателя стека и счетчике команд наблюдаем явный прогресс.Дальнейшими экспериментами устанавливаем, что форсированный ошибочный высокий уровень наблюдается на разрядах данных, по которым в последнем цикле были записаны единицы, и в которых в регистре r0 записаны единицы, причем явление явно триггерное — возникает при первом чтении после записи и удерживается на протяжении определенного времени, причем время это никак не связано с частотой МК (в пределах ошибки наблюдения), но имеет явно выраженную связь с температурой кристалла (при повышении температуры время удержания растет). Можно предположить, что сигнал управления выходным буфером верхнего каскада шины данных имеет нефорсированный неактивный уровень, и на него наводится сигнал с соответствующего разряда регистра, пока не произойдет перезарядка емкости токами утечки. Гипотеза хорошая, но триггерность, к сожалению, не объясняет, если кто нибудь придумает более подходящее объяснение — прошу в комменты. Ну а в практической части, так сказать в сухом остатке, перед чтением данных в регистр r0 записываем ноль и шина ведет себя как и должна, что подтверждается вышеприведенной осцилограммой, полученной при следующем коде
mov r0, #0xFFFFFFFF ; это псевдокод операции
ldrh r1,[r4] ; здесь наблюдается ошибка - фрагмент 1
strh r1,[r4] ; проводим запись с установлеными единицами, сигнал записи - синяя линия на нижней диаграмме
mov r0,#0x00000000
ldrh r1,[r4] ; а вот тут ошибки нет - фрагмент 2
Кстати, как и у О'Генри, ни королей ни капусты так и не было.