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

Я использую Freescale Kinetis K60 и использую IDE CodeWarrior (который, как я считаю, использует GCC для complier).

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

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

Я полагаю, что код в идеале будет что-то вроде этого:--2-->

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 (well 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, чтобы проверить его работу и результаты.

EDIT:

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

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 или иным образом используйте параметры условной компиляции для построения для разных целей без ассемблера.


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

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