Математика с фиксированной точкой с помощью ARM Cortex-M4 и компилятора gcc

Я использую Freescale Kinetis K60 и использую CodeWarrior IDE (которая, как мне кажется, использует GCC для компилятора).

Я хочу умножить два 32-битных числа (что дает 64-битное число) и сохранить только старшие 32 бита.

Думаю, правильная инструкция по сборке ARM Cortex-M4 - это инструкция SMMUL. Я бы предпочел получить доступ к этой инструкции из кода C, а не из сборки. Как мне это сделать?

Я предполагаю, что в идеале код будет примерно таким:

int a,b,c;

a = 1073741824;   // 0x40000000 = 0.5 as a D0 fixed point number
b = 1073741824;   // 0x40000000 = 0.5 as a D0 fixed point number

c = ((long long)a*b) >> 31;  // 31 because there are two sign bits after the multiplication
                             // so I can throw away the most significant bit

Когда я пробую это в CodeWarrior, я получаю правильный результат для c (536870912 = 0,25 как число D0 FP). Я нигде не вижу инструкции SMMUL, а умножение - это 3 инструкции (UMULL, MLA и MLA - я не понимаю, почему используется беззнаковое умножение, но это другой вопрос). Я также пробовал сдвиг вправо на 32, поскольку это может иметь больше смысла для инструкции SMMUL, но это не делает ничего другого.


person EpicAdv    schedule 03.12.2011    source источник
comment
Вы компилируете с включенной оптимизацией? Если ваш компилятор не генерирует оптимальный код, вы можете написать небольшую функцию сборки или использовать встроенную сборку из C.   -  person TJD    schedule 03.12.2011


Ответы (2)


Проблема, с которой вы сталкиваетесь при оптимизации этого кода:

08000328 <mul_test01>:
 8000328:   f04f 5000   mov.w   r0, #536870912  ; 0x20000000
 800032c:   4770        bx  lr
 800032e:   bf00        nop

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

это:

.thumb_func
.globl mul_test02
mul_test02:
    smull r2,r3,r0,r1
    mov r0,r3
    bx lr

называется с этим:

c = mul_test02(0x40000000,0x40000000);

дает 0x10000000

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

Хм, ну вы меня поймали. Я бы прочитал ваш код как указание компилятору повысить умножение до 64-битного. smull - это два 32-битных операнда, дающих 64-битный результат, а это не то, что требует ваш код ... но и gcc, и clang все равно использовали smull, даже если я оставил его как невызванную функцию, поэтому он не знал, что во время компиляции, что операнды не имели значащих цифр выше 32, они все еще использовали smull.

Возможно, причиной был сдвиг.

Ага, вот и все ..

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 31; 
    return(c);
}

дает

и gcc, и clang (хорошо clang перерабатывает r0 и r1 вместо использования r2 и r3)

08000340 <mul_test04>:
 8000340:   fb81 2300   smull   r2, r3, r1, r0
 8000344:   0fd0        lsrs    r0, r2, #31
 8000346:   ea40 0043   orr.w   r0, r0, r3, lsl #1
 800034a:   4770        bx  lr

но это

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b); 
    return(c);
}

дает это

gcc:

08000340 <mul_test04>:
 8000340:   fb00 f001   mul.w   r0, r0, r1
 8000344:   4770        bx  lr
 8000346:   bf00        nop

лязг:

0800048c <mul_test04>:
 800048c:   4348        muls    r0, r1
 800048e:   4770        bx  lr

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

Теперь, если вы сделаете это:

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 32; 
    return(c);
}

оба компилятора стали еще умнее, в частности clang:

0800048c <mul_test04>:
 800048c:   fb81 1000   smull   r1, r0, r1, r0
 8000490:   4770        bx  lr

gcc:

08000340 <mul_test04>:
 8000340:   fb81 0100   smull   r0, r1, r1, r0
 8000344:   4608        mov r0, r1
 8000346:   4770        bx  lr

Я вижу, что 0x40000000 рассматривается как число с плавающей запятой, где вы отслеживаете десятичный разряд, и это место является фиксированным. 0x20000000 будет иметь смысл в качестве ответа. Я пока не могу решить, работает ли этот 31-битный сдвиг универсально или только для этого одного случая.

Полный пример, использованный для вышеизложенного, находится здесь

https://github.com/dwelch67/stm32vld/tree/master/stm32f4d/sample01

и я запустил его на stm32f4, чтобы убедиться, что он работает и результаты.

РЕДАКТИРОВАТЬ:

Если вы передадите параметры в функцию вместо их жесткого кодирования внутри функции:

int myfun ( int a, int b )
{
     return(a+b);
}

Компилятор вынужден создавать код времени выполнения вместо того, чтобы оптимизировать ответ во время компиляции.

Теперь, если вы вызовете эту функцию из другой функции с жестко запрограммированными числами:

...
c=myfun(0x1234,0x5678);
...

В этой вызывающей функции компилятор может вычислить ответ и просто поместить его туда во время компиляции. Если функция myfun () глобальная (не объявлена ​​как статическая), компилятор не знает, будет ли ее использовать какой-либо другой код, который будет связан позже, поэтому даже рядом с точкой вызова в этом файле он оптимизирует ответ, он все равно должен генерировать фактическую функцию и оставьте его в объекте для вызова другого кода в других файлах, чтобы вы все еще могли проверить, что компилятор / оптимизатор делает с этим кодом C. Если вы, например, не используете llvm, где вы можете оптимизировать весь проект (по файлам), внешний код, вызывающий эту функцию, будет использовать реальную функцию, а не вычисленный ответ во время компиляции.

и gcc, и clang сделали то, что я описываю, оставили код времени выполнения для функции как глобальную функцию, но внутри файла он вычислил ответ во время компиляции и поместил жестко закодированный ответ в код вместо вызова функции:

int mul_test04 ( int a, int b )
{
    int c;
    c = ((long long)a*b) >> 31;
    return(c);
}

в другой функции того же файла:

hexstring(mul_test04(0x40000000,0x40000000),1);

Сама функция реализована в коде:

0800048c <mul_test04>:
 800048c:   fb81 1000   smull   r1, r0, r1, r0
 8000490:   0fc9        lsrs    r1, r1, #31
 8000492:   ea41 0040   orr.w   r0, r1, r0, lsl #1
 8000496:   4770        bx  lr

но там, где это называется, они жестко запрограммировали ответ, потому что у них была вся необходимая информация:

 8000520:   f04f 5000   mov.w   r0, #536870912  ; 0x20000000
 8000524:   2101        movs    r1, #1
 8000526:   f7ff fe73   bl  8000210 <hexstring>

Если вам не нужен жестко запрограммированный ответ, вам нужно использовать функцию, которая не находится в том же проходе оптимизации.

Управление компилятором и оптимизатором сводится к большой практике, и это не точная наука, поскольку компиляторы и оптимизаторы постоянно развиваются (в лучшую или в худшую сторону).
Изолируя небольшой фрагмент кода в функции, которую вы вызываете проблемы с другой стороны, более крупные функции с большей вероятностью будут нуждаться в кадре стека и вытеснять переменные из регистров в стек по мере продвижения, более мелкие функции могут не нуждаться в этом, и оптимизаторы могут в результате изменить способ реализации кода. Вы тестируете фрагмент кода одним способом, чтобы увидеть, что делает компилятор, а затем используете его в более крупной функции и не получаете желаемого результата. Если есть точная инструкция или последовательность инструкций, которые вы хотите реализовать .... Реализуйте их на ассемблере. Если вы нацеливались на конкретный набор инструкций в конкретном наборе инструкций / процессоре, избегайте игры, избегайте изменения вашего кода при изменении компьютеров / компиляторов / и т. Д. И просто используйте ассемблер для этой цели. при необходимости используйте ifdef или иным образом используйте параметры условной компиляции для сборки для разных целей без ассемблера.

person old_timer    schedule 03.12.2011
comment
Я использовал текущий компилятор gcc, ранее известный как codeourcery, и clang из llvm версии 3.0. - person old_timer; 03.12.2011
comment
Похоже, я ошибался. CodeWarrior, похоже, не использует GCC. Он ссылается на исполняемый файл под названием mwccarm, который, похоже, является компилятором, специфичным для Freescale. Я попытался определить умножение в функции и все еще не могу заставить компилятор использовать команду smull - на самом деле команда umull все еще существует. Я попробую скачать gcc или clang и посмотрю, что будет. - person EpicAdv; 03.12.2011
comment
Я не думаю, что мне удалось убедить компилятор скомпилировать отдельную функцию. Кажется, он оглядывается на то, где (и если) он вызван, и оптимизируется. Как я могу заставить его скомпилировать общую функцию, как вы здесь делаете? - person EpicAdv; 03.12.2011
comment
Это может быть компилятор metaware, который вы используете dwelch.com/gba/dhry.htm. Я напортачил с eval-копией metaware и некоторыми другими задолго до того, когда пытался найти хороший компилятор для большого пальца на заре создания набора инструкций для большого пальца. - person old_timer; 03.12.2011
comment
Спасибо. Немного поучимся здесь. 1) Смотрел в окне дизассемблирования всего скомпилированного проекта. По-видимому, если он никогда не вызывает эту функцию, он не включается в окончательную связанную версию. Если я смотрю на разборку только функции, я вижу, что создается. 2) Для этого компилятора, похоже, он не использует команду smmul, скорее, он умножает, а затем сдвигает отдельно (muls, asr, lsr и orr). Я протестировал его с помощью smmul в директиве сборки, и он работает так, как я хочу (за исключением 32-битного сдвига вместо 31). Могу ли я заставить его использовать smmul из C? - person EpicAdv; 05.12.2011
comment
Я думаю, что нужно убедить компилятор, что это действительно числа со знаком. Если вы жестко запрограммировали числа, например, 0x40000000 раз сами подписанные или неподписанные будут работать. правильно ли длинный длинный знак расширяет int? может это как-то связано с этим ... - person old_timer; 05.12.2011

GCC поддерживает актуальные типы с фиксированной точкой: http://gcc.gnu.org/onlinedocs/gcc/Fixed_002dPoint.html

Я не уверен, какую инструкцию он будет использовать, но это может облегчить вам жизнь.

person ams    schedule 03.12.2011
comment
На связанной странице: Не все цели поддерживают типы с фиксированной точкой. По моему опыту, очень немногие цели поддерживают типы с фиксированной точкой. - person ; 30.10.2013