uriy 4 14 февраля, 2019 Опубликовано 14 февраля, 2019 · Жалоба Есть рабочий проект для blackfin. Некоторые функции написаны на ассемблере. Хочу перенести код на ARM. Blackfin у нас работает на частоте 60 Мгц, по предварительным наработкам получается что ARM на частоте 180 МГц дает примерно такую же производительность. Для начала хочу в blackfin убрать ассемблерные функции, чтобы там все работало на си. В функции ниже в регистр R1 считывается элемент из входного массива. В регистр R5 считываются константы из таблицы синуса из 128 элементов. Цикл выполняется 128 раз. С моими тестовыми данными после десятка итараций R6 достигает насыщения 0x7FFF FFFF и затем перестает меняться. Даже если к A0 прибавляется отрицательный результат умножения R1*R5. Мне это кажется странным. Но это рабочий код. start_sine_loop: r6 = (a0 += r1.l * r5.l) || i0 +=m0 || r5.l = w[i0]; finish_sine_loop: r1 =w[p0++] (z); Код на си: Я рассчитываю получить в tmp_in тоже самое что в R6. Но результат совпадает только если отсчеты входного сигнала были с маленькой амплитудой. Почему-то сишный код не обеспечивает насыщение. Как на си записать эквивалент ассемблерного кода? for (j = 0, i = 0; i < 128; i++, j+= tone) { tmp_im = L_mac(tmp_im, sig_buf[i], dft_sine[j % 128]); } Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
_pv 52 14 февраля, 2019 Опубликовано 14 февраля, 2019 · Жалоба СИ сам по себе вроде бы ничего не знает про арифметику с насыщением, так что правильный ответ - ассемблерными вставками / интринсиками. а ещё если и целевая платформа не умеет в операции с насыщением, ну тогда проверяйте руками переполнение и если да, то меняйте результат на 0x7fffffff. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
uriy 4 14 февраля, 2019 Опубликовано 14 февраля, 2019 · Жалоба L_mac это встроенная в vdsp функция умножения с насыщением. Истинная проблема оказалась не в этом. После разгона ядра до 180 МГц сишный код стал нормально работать в железке с реальным сигналом без насыщения. Ассемблерный работает на частоте 60 МГц. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
dxp 32 15 февраля, 2019 Опубликовано 15 февраля, 2019 · Жалоба А вы кодогенерацию посмотрите (и нам покажите) - что там компилятор рожает. Сразу будут понятны причины и следствия. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
uriy 4 15 февраля, 2019 Опубликовано 15 февраля, 2019 · Жалоба #pragma optimize_for_speed //------------------------------------------------------------------------------------------ int32_t e1dft_l(int16_t sig_buf[], int16_t tone) { [FFA0C704] LINK 0x0 ; [FFA0C708] [ -- SP ] = ( R7:4 , P5:4 ) ; [FFA0C70A] R3.L = 0 ; [FFA0C716] P1 = R0 ; [FFA0C718] R2 = R1 + R3 ; [FFA0C71A] P2.L = 0x20cc ; [FFA0C71E] P2.H = 0xff80 ; uint16_t i, j; fract32 tmp_im, tmp_re, dft_sq, tmp_mult; fract16 r_im, r_re; tmp_im = 0; for (j = 0, i = 0; i < 128; i++, j+= tone) { tmp_im = L_mac(tmp_im, (fract16)sig_buf[i], (fract16)dft_sine[j % 128]); [FFA0C70E] R5 = 127 ( X ) ; [FFA0C712] R2 = R3 & R5 ; [FFA0C714] P0 = R2 ; [FFA0C722] R0 = R2 & R5 ; [FFA0C724] P4 = P2 + ( P0 << 1 ) ; [FFA0C726] P0 = R0 ; [FFA0C728] I0 = P1 ; [FFA0C72A] A0 = 0 || R0 = W [ P4 ] ( X ) || NOP ; [FFA0C732] R2.L = R1.L + R2.L ( NS ) || R3 = W [ P1 ++ ] ( X ) || NOP ; [FFA0C73A] P5 = 63 ; [FFA0C744] LSETUP ( __BEGIN__.P59L64L , 40 /*0xFFA0C76C*/ ) LC0 = P5 ; [FFA0C748] R3 = R2 & R5 ; [FFA0C74A] P5 = R3 ; [FFA0C74C] P0 = P2 + ( P0 << 1 ) ; [FFA0C74E] R2.L = R1.L + R2.L ( NS ) || R3 = W [ P0 ] ( X ) || NOP ; [FFA0C75E] R0 = R2 & R5 ; [FFA0C760] P0 = R0 ; [FFA0C762] P5 = P2 + ( P5 << 1 ) ; [FFA0C764] R2.L = R1.L + R2.L ( NS ) || R0 = W [ P5 ] ( X ) || NOP ; [FFA0C774] R3 = 32 ; [FFA0C77A] P0 = P2 + ( P0 << 1 ) ; [FFA0C77C] R6.L = R1.L + R3.L ( NS ) || R2 = W [ P0 ] ( X ) || R3.L = W [ I0 ++ ] ; } r_im = round(tmp_im); tmp_re = 0; for (j = 32,i = 0; i < 128; i++, j+= tone) { tmp_re = L_mac(tmp_re, (fract16)sig_buf[i], (fract16)dft_sine[j % 128]); [FFA0C776] R2 = R3 & R5 ; [FFA0C778] P1 = R2 ; [FFA0C784] R7 = R6 & R5 ; [FFA0C78A] P1 = P2 + ( P1 << 1 ) ; [FFA0C78C] P0 = R7 ; [FFA0C790] A0 = 0 || R2 = W [ P1 ] ( X ) || NOP ; [FFA0C798] P1 = 63 ; [FFA0C79A] R4.L = R1.L + R6.L ( NS ) || R6.L = W [ I0 ++ ] || NOP ; [FFA0C7AA] LSETUP ( __BEGIN__.P59L60L , 40 /*0xFFA0C7D2*/ ) LC0 = P1 ; [FFA0C7AE] R0 = R4 & R5 ; [FFA0C7B0] P1 = R0 ; [FFA0C7B2] P0 = P2 + ( P0 << 1 ) ; [FFA0C7B4] R2.L = R1.L + R4.L ( NS ) || R0 = W [ P0 ] ( X ) || NOP ; [FFA0C7C4] R0 = R2 & R5 ; [FFA0C7C6] P0 = R0 ; [FFA0C7C8] P1 = P2 + ( P1 << 1 ) ; [FFA0C7CA] R4.L = R1.L + R2.L ( NS ) || R0 = W [ P1 ] ( X ) || NOP ; [FFA0C7DA] P1 = P2 + ( P0 << 1 ) ; [FFA0C7DC] R1 = R1.H * R1.H || R0 = W [ P1 ] ( X ) || NOP ; } r_re = round(tmp_re); dft_sq = L_mult(r_im, r_im); dft_sq = L_mac(dft_sq, r_re, r_re); return (dft_sq); [FFA0C7F2] ( R7:4 , P5:4 ) = [ SP ++ ] ; [FFA0C7F8] UNLINK ; } Вот вся проблемная функция в режиме mixed А ниже рукописный ассемблерный аналог .section L1_code; .global _e1dft_l; .align 2; .extern _dft_sine; _e1dft_l: link 0; [--sp] = (r7:5, p5:5); [--sp] = lc0; [--sp] = lb0; [--sp] = lt0; // r0 - addres inpute buffer // r1 - number tone i0.l = lo(_dft_sine); i0.h = hi(_dft_sine); b0 = i0; p0 = r0; p5 = 256; // 256 = 128 * 2byte l0 = p5; p5 = 128; r1 <<= 1; m0 = r1; // m0 = r1 = STR_TON or STR_TON_PRM #if defined(__WORKAROUND_BF532_ANOMALY_050000245) NOP; NOP; #endif r1 = w[p0++] (z); // R1 has value of first element in buffer a0 = 0; nop; r5.l = w[i0]; // R5 addres of dft_sine table i0 +=m0; lsetup (start_sine_loop, finish_sine_loop) lc0 = p5; start_sine_loop: r6 = (a0 += r1.l * r5.l) || i0 +=m0 || r5.l = w[i0]; finish_sine_loop: r1 =w[p0++] (z); // read next item from buffer into R1 p0 = r0; // P0 is pointer to first buffer element p5 = 128; i0 = b0; // i0 is address of dft_sine table m1 = 64; i0 += m1; //(32*2); // ( 128 / 4 ) * 2 r5.l= w[i0]; // r5.l load first elemnt of dft_sine table in r5.l i0 +=m0; a1 = 0; r1 = w[p0++] (z); // load first buffer elemnt in r1 lsetup (start_cosine_loop, finish_cosine_loop) lc0 = p5; start_cosine_loop: r7 = (a1 += r1.l * r5.l) || i0 +=m0 || r5.l = w[i0]; finish_cosine_loop: r1 =w[p0++] (z); r1.l= r7(rnd); r5.l= r6 (rnd); a0 = r1.l * r1.l; r0 = (a0 += r5.l * r5.l); l0 = 0; lt0 = [sp++]; // modified lb0 = [sp++]; lc0 = [sp++]; (r7:5, p5:5) = [sp++]; p0 = [fp + 4]; unlink; jump (p0); ._e1dft_l.end: Мне уже кажется плохой идея перенести это на ARM Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 183 15 февраля, 2019 Опубликовано 15 февраля, 2019 · Жалоба 22 часа назад, uriy сказал: Blackfin у нас работает на частоте 60 Мгц, по предварительным наработкам получается что ARM на частоте 180 МГц дает примерно такую же производительность. start_sine_loop: r6 = (a0 += r1.l * r5.l) || i0 +=m0 || r5.l = w[i0]; finish_sine_loop: r1 =w[p0++] (z); Я не знаком с системой команд blackfin, но насколько я понимаю указанный цикл должен тратить 2 такта на одну итерацию цикла? Если так, то непонятно какие "наработки" позволили сделать заключение, что ARM-овский аналог этого цикла можно уложить в 6 тактов? (180/60*2такта) В этом цикле две выборки из памяти (по разным указателям, которые нельзя объединить в одно чтение одной командой ARM), а это уже 2*2 такта ARM; также для ARM потребуется как минимум 1 такт на модификацию переменной цикла и 1такт + (0...x)тактов на prefetch при выполнении условного перехода в конце цикла. Итого - уже 6 тактов. А ведь нужно ещё собственно и саму MAC-операцию выполнить и нужно сделать i0+=m0 (если конечно m0 нельзя свести к константе), и операцию насыщения.... Хотя конечно если расположить оба чтения памяти подряд, то они возможно они будут pipelined и дадут не 4, а только 3 такта. И попробовать развернуть эти циклы (раз там всего 128 итераций) в линейный код, убрав счётчики цикла и команды перехода (код тогда придётся расположить в ОЗУ; или выбрать МК с широкой шиной (256 бит) к флеш-памяти). Может только так что-то и получится. Но о си тут уже никакой речи не идёт - Вам надо глубоко изучать систему команд ARM и писать на ассемблере. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
uriy 4 15 февраля, 2019 Опубликовано 15 февраля, 2019 · Жалоба Под наработками имелось ввиду другие проекты. Вокодер MELP2400 на блэкфине на 60 МГц и на STM32F446 на 180 МГц примерно одинаково нагружают процессор. Я согласен с вами, именно из-за отсутствия аппаратных циклов в ARM перенос кода теперь кажется бредовой идеей. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 183 15 февраля, 2019 Опубликовано 15 февраля, 2019 · Жалоба 14 минут назад, uriy сказал: Под наработками имелось ввиду другие проекты. Вокодер MELP2400 на блэкфине на 60 МГц и на STM32F446 на 180 МГц примерно одинаково нагружают процессор. Это не значит, что они имеют одинаковый алгоритм. Там скорее всего код разный, выполняющий одинаковые функции. И ARM там может догонять за счёт бОльшей разрядности операций и изменённого алгоритма. А Вы тут хотите переделать ассемблерный код один-в-один, не меняя алгоритма. Тогда придётся брать бОльший коэфф. запаса. В Вашем случае можно попробовать подумать ещё над тем чтобы в таких циклах сделать пакетное чтение одной командой нескольких значений в группу регистров (например 4 регистра), а потом за одну итерацию выполнить действия 4 итераций DSP-кода. Так получится уже только 1+4+1+4 тактов на все операции чтения для такой счетверённой итерации и затраты на переходы в 4 раза меньше. Для этого возможно нужно перераспределить данные в памяти более удобно. Ну т.е. - без переработки алгоритма не обойтись. Цитата Я согласен с вами, именно из-за отсутствия аппаратных циклов в ARM перенос кода теперь кажется бредовой идеей. Не только. На DSP чтение/запись в память можно параллелить с операциями в АЛУ не тратя таким образом на них ни такта лишнего. А в ARM каждая отдельная команда обращения к памяти - 2 такта. Ну и другие плюшки тож. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться