Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: как побороть WinAVR?
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Cредства разработки для МК > GNU/OpenSource средства разработки
VDV
написана функция:

volatile int32_t _corr[2 * 8];//result of correlations
volatile uint8_t _corr_index;//index for sample table
int8_t PROGMEM _dtmf_samples[] = {...};

int8_t d = ADCH;
for( uint8_t i = 16; i--; )
{
int16_t mul = d * pgm_read_byte( &_dtmf_samples[_corr_index] );
_corr[i] += mul;
}

в результате генерится совершенно безобразный код.
умножение производится в 32-х битном виде, хотя явно написано умножение 2-х байтов
включена оптимизация O3

если массив объявить как int8_t, то умножение делается для 2-х байтов, но!!!!
используется mul вместо muls (хотя написано, что величины знаковые!!!)
и в результате он зануляет старший байт, даже если идет приведение результата операции к int16_t

как побороть WinAVR?

что хочется:
сделать знаковое умножение 2-х байтов, получить 16-ти битное значение.
затем расширить его до 32-х бит,
сложить с 32-х битной величиной из массива и запомнить в массиве результат.

и еще:
ткните, плиз, носом где понятно написано как делать ассемблерные вставки.
собственно, непонятно, как из асма взять данные из Сишной переменной и в нужную Сишную переменную отдать
aesok
Цитата(VDV @ Aug 7 2008, 21:01) *
в результате генерится совершенно безобразный код.
умножение производится в 32-х битном виде, хотя явно написано умножение 2-х байтов
включена оптимизация O3


Скомпилировал Ваш код, у меня 16-битное умножение:

Код
    mul r30,r28    ;  33    *mulhi3_enh    [length = 7]
    movw r24,r0
    mul r30,r29
    add r25,r0
    mul r31,r28
    add r25,r0
    clr r1


Какая версия AVR-GCC?

Цитата(VDV @ Aug 7 2008, 21:01) *
и еще:
ткните, плиз, носом где понятно написано как делать ассемблерные вставки.
собственно, непонятно, как из асма взять данные из Сишной переменной и в нужную Сишную переменную отдать


avr-libc-user-manual + исходники avr-libc. Ну и если что в деталях не понятно - вопросы на форумах.

Анатолий.
demiurg_spb
Цитата(VDV @ Aug 7 2008, 21:01) *
что хочется:
сделать знаковое умножение 2-х байтов, получить 16-ти битное значение.

В то время как pgm_read_byte возвращает uint8_t
Цитата(VDV @ Aug 7 2008, 21:01) *
затем расширить его до 32-х бит,
сложить с 32-х битной величиной из массива и запомнить в массиве результат.

Что gcc 4.1.2 отлично делает...
Советую Вам _всегда_ использовать оптимизацию по размеру:
OPT = s
Цитата(VDV @ Aug 7 2008, 21:01) *
ткните, плиз, носом где понятно написано как делать ассемблерные вставки.

http://www.nongnu.org/avr-libc/user-manual/inline_asm.html
777777
Цитата(VDV @ Aug 7 2008, 21:01) *
ткните, плиз, носом где понятно написано как делать ассемблерные вставки.

Ассемблерные вставки нарушают работу оптимизатора. Поэтому лучше ассемблерную часть скомпоновать в отдельную функцию и вынеси в отдельнй файл.
Цитата(VDV @ Aug 7 2008, 21:01) *
собственно, непонятно, как из асма взять данные из Сишной переменной и в нужную Сишную переменную отдать

Что непонятного? В ассемблере переменная называется так же, как и в Си.

ЗЫ. А с оптимизацей -Os не пробовал?
ЗЗЫ. Откуда у народа такая мания - создавать переменные с подчеркиванием в начале? Ну везде же написано, что это не рекомендуется, так как такие имена зарезервированы для системных целей.
AHTOXA
Цитата(777777 @ Aug 8 2008, 10:36) *
Ассемблерные вставки нарушают работу оптимизатора.


В gcc не нарушают. Там грамотно всё сделано. Почитайте по ссылке выше.
aesok
Цитата(VDV @ Aug 7 2008, 21:01) *
int8_t d = ADCH;
for( uint8_t i = 16; i--; )
{
int16_t mul = d * pgm_read_byte( &_dtmf_samples[_corr_index] );
_corr[i] += mul;
}


Макрос pgm_read_byte возвращает значение типа uint8_t (unsigned char),
переменная d имеет int8_t то есть signed char. Не смотря на то что AVR
имеет команду MULSU, avr-gcc ее не использует и вместо умножения (s)8 * (u)8
= 16, выполняет умножение 16*16=16. 8-битные умножения signed на signed и
unsigned на unsigned выполняются как 8 * 8 = 16. Вы можете попробовать
изменить тип d на uint8_t и для умножения компилятор сгенерирует код с
использованием инструкции MUL:

Код
    mul r30,r16;  30    umulqihi3    [length = 3]
    movw r24,r0
    clr r1



Вот патч для GCC для выполнения умножения 8-битных signed на unsigned с
помощью MULSU.


Код
Index: gcc/config/avr/avr.md
===================================================================
--- gcc/config/avr/avr.md    (revision 134646)
+++ gcc/config/avr/avr.md    (working copy)
@@ -906,6 +920,17 @@
   [(set_attr "length" "3")
    (set_attr "cc" "clobber")])

+(define_insn "usmulqihi3"
+  [(set (match_operand:HI 0 "register_operand" "=r")
+    (mult:HI (zero_extend:HI (match_operand:QI 1 "register_operand" "a"))
+         (sign_extend:HI (match_operand:QI 2 "register_operand" "a"))))]
+  "AVR_HAVE_MUL"
+  "mulsu %2,%1
+    movw %0,r0
+    clr r1"
+  [(set_attr "length" "3")
+   (set_attr "cc" "clobber")])
+
(define_expand "mulhi3"
   [(set (match_operand:HI 0 "register_operand" "")
    (mult:HI (match_operand:HI 1 "register_operand" "")


Можно на написать aыm вставку для 8-битного умножения signed на unsigned.

Анатолий.
demiurg_spb
Цитата(aesok @ Aug 8 2008, 10:37) *
Макрос pgm_read_byte возвращает значение типа uint8_t (unsigned char),
переменная d имеет int8_t то есть signed char. Не смотря на то что AVR
имеет команду MULSU, avr-gcc ее не использует и вместо умножения (s)8 * (u)8
= 16, выполняет умножение 16*16=16. 8-битные умножения signed на signed и
unsigned на unsigned выполняются как 8 * 8 = 16. Вы можете попробовать
изменить тип d на uint8_t и для умножения компилятор сгенерирует код с
использованием инструкции MUL:

Можно на написать asm вставку для 8-битного умножения signed на unsigned.

У него массив в PROGMEM int8_t !!!
Надо просто привести тип до умножения и будет счастье...

Код
(int8_t)pgm_read_byte(&_dtmf_samples[idx])

или написать макрос для чтения int8_t:
Код
#define pgm_read_sbyte(x)  ((int8_t)pgm_read_byte(x))
VDV
спасибо за ответы,
я ошибся написав, что он делает 32 битное умножение, сорри, он делает 16-битное с 32-х битным результатом. при этом делает 3 умножения вместо 1!
явное приведение типов при умножении к знаковым не помогает - перепробовал все, что пришло в голову.

Os оптимизация работает плохо.
попробуйте, например, написать код:
uint16_t d = ...;
d >>= 1;
d >>= 1;
d >>= 1;

и посмотрите что он накомпиллит.
с O3 тоже не сахар, но все получше код получается.
//==================================
в общем, к сожалению, в итоге я так и не понял, как сделать так, чтобы
компиллятор сначала умножил два 8-ми битовых знаковых числа,
затем получившееся 16-ти битное значение сложил 32-х битным.
Использую WinAVR20080610 в паре с AVR studio 4.14

volatile int32_t _corr[2 * 8];//result of correlations
volatile uint8_t _corr_index;//index for sample table
int8_t PROGMEM _dtmf_samples[] = {...};

int8_t d = ADCH;
for( uint8_t i = 16; i--; )
{
int16_t mul = (int8_t)d * (int8_t)pgm_read_byte( &_dtmf_samples[_corr_index] );
_corr[i] += (int32_t)mul;
}

такой код не приводит к желаемому результату
aesok
Цитата(VDV @ Aug 8 2008, 12:49) *
такой код не приводит к желаемому результату


-O3:
Код
    mul r22,r14    ;  
    movw r24,r0
    mul r22,r15
    add r25,r0
    mul r23,r14
    add r25,r0
    clr r1


-Os:
Код
    muls r30,r16    ;  
    movw r24,r0
    clr r1


Анатолий.
ReAl
Цитата(VDV @ Aug 8 2008, 11:49) *
Os оптимизация работает плохо.
попробуйте, например, написать код:
uint16_t d = ...;
d >>= 1;
d >>= 1;
d >>= 1;
и посмотрите что он накомпиллит.
с O3 тоже не сахар, но все получше код получается.
А что тут пробовать?
Для трёх сдвигов при -Os будет цикл, так как он действительно займёт меньше места, пусть и всего на одну команду.
Для -O3 будет линейный код. Для -O2 тоже.
Нет смысла для AVR использовать -O3, он слишком много разворачивает в линию, размножает тела циклов и т.п.
Разве что (я в это не очень верю, но не исключаю) для отдельных особых функций, вынесенных ради этого в отдельный файл.

Цитата(VDV @ Aug 8 2008, 11:49) *
Использую WinAVR20080610 в паре с AVR studio 4.14

volatile int32_t _corr[2 * 8];//result of correlations
volatile uint8_t _corr_index;//index for sample table
int8_t PROGMEM _dtmf_samples[] = {...};

int8_t d = ADCH;
for( uint8_t i = 16; i--; )
{
int16_t mul = (int8_t)d * (int8_t)pgm_read_byte( &_dtmf_samples[_corr_index] );
_corr[i] += (int32_t)mul;
}
такой код не приводит к желаемому результату

WinAVR-20071221
-Os
Код
    muls r30,r16
    movw r24,r0
    clr r1
    clr r26
    sbrc r25,7
    com r26
    mov r27,r26
    add r24,r20
    adc r25,r21
    adc r26,r22
    adc r27,r23
Я не стал цитировать чтение-запись массивов и организацию цикла, только умножение-суммирование.
Да, для -O2 и -O3 почему-то делает 16-битное умножение. "ну не знаю", таки недоработка оптимизатора в этих режимах. Согласен, плохо.
Повторю сказанное другими - в подавляющем большинстве случаев лучше использовать -Os.
Изредка -O2. Иногда лучше фрагмент на асме написать, чем от -Os отказываться.
-O3 явно не для AVR. Для Вашего примера разворачивать цикл на 16 проходов в одну линию, дублируя 16 раз тело цикла - нафиг, нафиг. Ускорение не стоит увеличения объёма.

Собственно, есть всего четыре варианта.
1. Использовать avr-gcc и терпеть некоторые его недостатки.
2. Использовать avr-gcc и НЕ терпеть некоторые его недостатки, включиться в работу по его улучшению.
3. Использовать спёртый IAR.
4. Использовать купленный IAR.
VDV
мда....
ни с О3 ни с Os код нормально не генерит так чтоб нормально было.

С Os он циклы строит, где ему явно прописано 3 раза сдвинуть (я уж не говорю, что он зачем-то перегрузку регистров в другую пару делает. В O3 тоже, кстати, зачем-то регистры туда-сюда гоняет)

O3 дает 3 умножения вместо одного
Os делает байтовое умножение, только регистры туда -сюда тусует зачем-то

блин, а у меня это критичные места по времени.
делать асмовские вставки в сишные код ну так не хочется.
от асма изначально и отказался только потому, чтоб переносимость была.

вместо программирования борьба с компилем.

придется, видимо, отказываться от 8-бит контроллера и переходить на ARM sad.gif
компиль все ресурсы убивает в никуда sad.gif
aesok
Цитата(VDV @ Aug 8 2008, 17:39) *
блин, а у меня это критичные места по времени.


НЕВЕРЮ!!!!!!
Код
int8_t d = ADCH;
for( uint8_t i = 16; i--; )
{
int16_t mul = d * pgm_read_byte( &_dtmf_samples[_corr_index] );
_corr[i] += mul;
}


Если б дейсвительно это было для Вас критично по времени, то первым делом Вы бы вынесли "int16_t mul = d * pgm_read_byte( &_dtmf_samples[_corr_index] );" из цикла.

Анатолий.
VDV
Цитата(aesok @ Aug 8 2008, 18:04) *
НЕВЕРЮ!!!!!!
Код
int8_t d = ADCH;
for( uint8_t i = 16; i--; )
{
int16_t mul = d * pgm_read_byte( &_dtmf_samples[_corr_index] );
_corr[i] += mul;
}


Если б дейсвительно это было для Вас критично по времени, то первым делом Вы бы вынесли "pgm_read_byte( &_dtmf_samples[_corr_index] )" из цикла.

Анатолий.


операция lpm r0, z выполняется быстро и умножение всего 2 такта.
так что весь кусок поги занял бы очень мало времени
гораздно быстрее, чем 3 умножения, которые туда лепит компиль или, при другой оптимизации, дважды туда-сюда гоняет данные из регистров.

и раположение таблицы в памяти программ - это обычное дело.
самый обычный корреляционный алгоритм. еще на 8051 делал с его 1 мипсом.


и уж организация цикла для тройного сдвига явно медленнее, чем три раза подряд сдвинуть. + экономии памяти точно никакой - достаточно написать на асме код и убедиться в этом.
aesok
Цитата(VDV @ Aug 8 2008, 18:14) *


А вот ЭТО "&_dtmf_samples[_corr_index]" сколько выполняеться Вы не заметили?

И вообще зачем в цикле выполнять код который не зависит от пременной цикла????

Во всех учебниках советуют такой код выносить из цикла.



достаточно написать на асме код и убедиться в этом:

Код
    ldi r18,3;  85    *lshrhi3_const/5    [length = 5]
1:    lsr r25
    ror r24
    dec r18
    brne 1b


5 инструкций. 3 последовательных сдвига на 1 это 6. Что короче?

Поэтому на -Os цикл, на -O3 - 3 сдвига.
Анатолий.
VDV
код приведен для пояснения смысла проблемы, а не объяснения работы программы.
ес-но правильный цикл несколько другой.
чем проще код в примере, тем проще понять суть проблемы.

по поводу сдвига, если на асме:
если написать подряд, то получается 6 команд и 6 тактов
если написать цикл, то получается 5 команд и 17 тактов

если скомпиллить O3, получаем 10 команд и 10 тактов
если скомпиллить Os, получаем 9 команд и 21 такт
лишние команды - это пересылки туда-сюда до сдвига и обратно после сдвига (совершенно не понимаю, зачем компиль их делает)

итого:
проигрышь по объему и по скорости от 2-х до почти 4-х раз
AHTOXA
Цитата(VDV @ Aug 8 2008, 20:57) *
проигрышь по объему и по скорости от 2-х до почти 4-х раз


ИМХО, некоторым людям вредно знать ассемблер... Вы посчитайте, сколько циклов у вас есть на выполнение нужной работы, и посмотрите, успеваете ли. Если успеваете - какой смысл горевать по лишним тактам? Если не успеваете - пишите на асме/меняйте компилер/шлифуйте алгоритм.
_Pasha
Цитата(777777 @ Aug 8 2008, 08:36) *
Что непонятного? В ассемблере переменная называется так же, как и в Си.

lol.gif До тех пор, пока ее не объявят extern в асмовой программе, она называется Undefined reference to ...

автору:
Если серьезно, напишИте же Вы все критическое на асме и не грузитесь. Делов-то...
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.