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

Я использую Freescale Kinetis K60 и использую IDW CodeWarrior (который, я считаю, использует 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, но это не делает ничего другого.

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);
}

дает это

НКА:

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

НКА:

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>

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

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

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

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

Другие вопросы по тегам