Как добавить задержку в 150 циклов на ARM?

Изменение подтягивающих / понижающих резисторов для выводов GPIO на Raspberry Pi требует ожидания 150 циклов после включения и дисбаланса тактового сигнала в соответствии со спецификациями. Сделать это немного дольше не повредит, но использование таймера для ожидания намного дольше, поэтому я не хочу этого делать. Итак, у меня есть простой цикл занятости:

for (int i = 0; i < 150; ++i) { asm volatile (""); }

0:   e3a03096        mov     r3, #150        ; 0x96
4:   e2533001        subs    r3, r3, #1
8:   1afffffd        bne     4 <foo+0x4>

Это повторяется 150 раз, выполняя 300 инструкций. Без кеширования инструкций и без предсказания переходов это определенно более 150 циклов. Но как только они включены, цикл начинает работать намного быстрее, я думаю, быстрее, чем 150 циклов.

Итак, как мне ждать около 150 циклов с включенным кешем инструкций и прогнозированием ветвления или без него? Примечание: в худшем случае это могут быть 2 функции, delay_no_cache () и delay_cache ()

Это не дубликат Как задержать ARM Cortex M0 + на n циклов без таймера?, поскольку кэш инструкций и предсказание ветвления полностью отбрасывают время. Время также различается между Raspberry Pi (ARMv6) и Raspberry Pi2 (ARMv7).

Кто-нибудь знает время выполнения (с кешем и без него), если бы можно было вставить DMB, DSB (я предполагаю, что это будут NOP, поскольку доступ к оперативной памяти отсутствует) или инструкцию ISB в цикл? Может ли это предотвратить эффект бегства при включении кешей?


person Goswin von Brederlow    schedule 07.04.2015    source источник
comment
возможный дубликат Как отложить ARM Cortex M0 + на n циклов без таймера?   -  person samgak    schedule 07.04.2015
comment
Не дубликат. В идеале я ищу цикл, который имеет (почти) одинаковую синхронизацию инструкций с кэшированием инструкций и предсказанием ветвлений и без них. Я знаю, что приведенный выше простой цикл работает примерно в 500 раз быстрее с кешированием инструкций и предсказанием ветвлений.   -  person Goswin von Brederlow    schedule 07.04.2015


Ответы (2)


Я сделал несколько измерений на моем Raspberry PI 2 running delay () для 1–1000000 циклов с коэффициентом 10 и вычислил количество циклов по истечении времени. Это показывает, что без кешей всего одного прохода через пустой цикл более чем достаточно для задержки в 150 циклов (это просто sub + bcs). Один NOP (в последовательности из 150) занимает 32 (всего 5030) циклов без кешей и 1,5 цикла с (всего 226,5). Orr; add; и; mov; orr; add; и; mov; loop также показывает, насколько конвейерным и суперскалярным является ЦП, занимая всего 0,15 цикла на код операции. Не так хорошо, чтобы получить цикл хорошего времени.

В заключение я должен отказаться и просто использовать задержку на основе таймера. На самом деле это быстрее без кешей, чем цикл, который занимает 150 циклов с кешами.

void delay(uint32_t count) {
    uint32_t a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0;
    while(count--) {
// branch icache dcache cycles/loop
// no     no     no     ~507
// no     no     yes      43.005
// no     yes    no        1.005
// no     yes    yes       1.005
// yes    no     no     ~507
// yes    no     yes      43.005
// yes    yes    no        1.005
// yes    yes    yes       1.005
// asm ("");

// branch icache dcache cycles/loop
// no     no     no     ~750
// no     no     yes      67.500
// no     yes    no       16.500
// no     yes    yes      16.500
// yes    no     no     ~750
// yes    no     yes      67.500
// yes    yes    no       16.500
// yes    yes    yes      16.500
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");

// branch icache dcache cycles/loop
// no     no     no     ~505
// no     no     yes      43.500
// no     yes    no        1.500
// no     yes    yes       1.500
// yes    no     no     ~505
// yes    no     yes      43.500
// yes    yes    no        1.500
// yes    yes    yes       1.500
 asm ("orr %0, %0, %0" : "=r" (a) : "r" (a));
 asm ("add %0, %0, %0" : "=r" (b) : "r" (b));
 asm ("and %0, %0, %0" : "=r" (c) : "r" (c));
 asm ("mov %0, %0" : "=r" (d) : "r" (d));
 asm ("orr %0, %0, %0" : "=r" (e) : "r" (e));
 asm ("add %0, %0, %0" : "=r" (f) : "r" (f));
 asm ("and %0, %0, %0" : "=r" (g) : "r" (g));
 asm ("mov %0, %0" : "=r" (h) : "r" (h));

// branch icache dcache cycles/loop
// no     no     no     ~1010
// no     no     yes       85.005
// no     yes    no        18.000
// no     yes    yes       18.000
// yes    no     no     ~1010
// yes    no     yes       85.005
// yes    yes    no        18.000
// yes    yes    yes       18.000
// isb();

// branch icache dcache cycles/loop
// no     no     no     ~5075
// no     no     yes      481.501
// no     yes    no       141.000
// no     yes    yes      141.000
// yes    no     no     ~5075
// yes    no     yes      481.501
// yes    yes    no       141.000
// yes    yes    yes      141.000
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
    }
}
person Goswin von Brederlow    schedule 18.04.2015
comment
asm ("orr %0, %0, %0" : "=r" (a) : "r" (a)); не использует %1, поэтому он может использовать весь мусор, который был в выходном операнде, в качестве входных данных, если компилятор случайно выберет разные регистры ввода / вывода. У вас уже есть достаточный параллелизм на уровне инструкций, чтобы цепочки зависимостей с циклическим переносом не ограничивали задержку, так что это не имеет особого значения. (Я скептически отношусь к 0,15 цикла на инструкцию. Даже AMD Ryzen может управлять только 5 инструкциями за такт (или 6 мопов, если есть многоэлементные инструкции), а процессоры Intel x86 имеют суперскалярную ширину 4. Ширина 6,66 маловероятна. - person Peter Cordes; 01.09.2017
comment
О, думаю, я знаю, что случилось. вы не использовали asm volatile, и ваша функция не использует a,b,c,d,e,f,g,h после цикла, поэтому gcc оптимизировал все ваши asm операторы. (не volatile asm с хотя бы одним выходным операндом считается чистой функцией своих входов без побочных эффектов. gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Volatile). Да, godbolt.org/g/Qg7r8N подтверждает, что именно это произошло, если вы скомпилировали с -O1 или выше. Ваша функция даже компилируется нормально на x86, потому что asm оптимизируется: P Всегда проверяйте вывод asm компилятора. - person Peter Cordes; 01.09.2017

Для задержки вам может потребоваться ПОВТОР МАКРОФУНКЦИИ. Используя цикл во время выполнения, всегда будет оптимизация, и сам цикл тоже стоит времени. Вы можете повторить макрос NOP 150 раз. Не будет ни оптимизации, ни лишних циклов.

Вот шаблон повторяющегося макроса:

#define MACRO_CMB( A , B)           A##B
#define M_RPT(__N, __macro)         MACRO_CMB(M_RPT, __N)(__macro)

#define M_RPT0(__macro)
#define M_RPT1(__macro)             M_RPT0(__macro)   __macro(0)
#define M_RPT2(__macro)             M_RPT1(__macro)   __macro(1)
#define M_RPT3(__macro)             M_RPT2(__macro)   __macro(2)
...
#define M_RPT256(__macro)           M_RPT255(__macro) __macro(255)

Вы можете определить свою инструкцию NOP следующим образом:

#define MY_NOP(__N)                 __asm ("nop");    // or sth like "MOV R0,R0"

Затем вы можете повторить инструкцию 150 раз, просто вызвав это :

M_RPT(150, MY_NOP);

Он действительно будет выполнен 150 раз.

Надеюсь, это помогло.

person kevin wu    schedule 08.04.2015
comment
Это совсем не помогает. Проблема не в оптимизации, поскольку asm () предотвращает это. И дело не в том, что сама петля стоит. Эта стоимость может быть учтена в задержке или минимизирована путем развертывания. Проблема в том, что 150 NOP занимают 150 циклов без кеша, но только 15 циклов с (или что-то в этом роде). - person Goswin von Brederlow; 09.04.2015