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

StackOverflow https://stackoverflow.com/questions/8364420

Вопрос

Я использую Freescale Kinetis K60 и IDE 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, но это ничего не меняет.

Это было полезно?

Решение

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

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

Хм, ну, в этом ты меня раскусил.Я бы прочитал ваш код как указание компилятору увеличить значение multiply до 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>

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

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

Другие советы

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top