2011-12-03 42 views
8

Uso Freescale Kinetis K60 y uso el CodeWarrior IDE (que creo que usa GCC para el compilador).Matemáticas de punto fijo con ARM Cortex-M4 y el compilador de gcc

Quiero multiplicar dos números de 32 bits (que da como resultado un número de 64 bits) y solo retener los 32 bits superiores.

Creo que las instrucciones de montaje correctas para ARM Cortex-M4 son las instrucciones SMMUL. Preferiría acceder a esta instrucción desde el código C en lugar del ensamblaje. ¿Cómo hago esto?

me imagino el código ideal sería algo como esto:

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 

Cuando intento esto en CodeWarrior, me sale el resultado correcto para c (536870912 = 0,25 como un número D0 FP). No veo la instrucción SMMUL en ninguna parte y la multiplicación son 3 instrucciones (UMULL, MLA y MLA - No entiendo por qué está usando una multiplicación sin signo, pero esa es otra pregunta). También probé un cambio a la derecha de 32 ya que podría tener más sentido para la instrucción SMMUL, pero eso no hace nada diferente.

+0

¿Está compilando con optimización? Si su compilador no está generando un código óptimo, puede escribir una pequeña función de ensamblaje o usar un ensamblado en línea de C. – TJD

Respuesta

6

El problema que se obtiene con la optimización de que el código es:

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

su doesnt código de tiempo de ejecución hacer nada por lo que el optimizador solo puede calcular la respuesta final.

esto:

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

llamada con esto:

c = mul_test02(0x40000000,0x40000000); 

da 0x10000000

UMULL da el mismo resultado porque está utilizando números positivos, los operandos y los resultados son positivos por lo no entra en las diferencias firmadas/sin firmar.

Hmm, bueno me tienes en este. Leería su código diciéndole al compilador que promoviera la multiplicación a 64 bits. smull es dos operandos de 32 bits que dan un resultado de 64 bits, que no es lo que tu código está pidiendo ... pero tanto gcc como clang usaron el smull de todos modos, incluso si lo dejé como una función no llamada, por lo que no lo sabía Tiempo de compilación en el que los operandos no tenían dígitos significativos por encima de 32, todavía usaban smull.

Tal vez el cambio fue la razón.

Sí, eso fue todo ..

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

da

tanto gcc y sonido metálico (bien sonido metálico recicla r0 y r1 en lugar de utilizar r2 y 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 

pero esto

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

da esta

gcc:

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

sonido metálico:

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

Así que con el desplazamiento de bit los compiladores se dan cuenta de que sólo está interesado en la parte superior de los resultados para que puedan desprenderse de la parte superior de los operandos que significa Smull puede ser usado.

Ahora bien, si usted hace esto:

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

los compiladores se ponen aún más inteligente, en particular sonido metálico:

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 

puedo ver que 0x40000000 considerados como un flotador donde se realiza un seguimiento del lugar decimal, y ese lugar es una ubicación fija. 0x20000000 tendría sentido como la respuesta. Todavía no puedo decidir si ese cambio de 31 bits funciona universalmente o solo para este caso.

Un ejemplo completo utilizado para el anterior es que aquí

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

y lo hice ejecutarlo en un stm32f4 para verificar su funcionamiento y los resultados.

EDIT:

Si sale de los parámetros en la función en lugar de codificar dentro de la función:

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

El compilador se ve obligado a hacer que el código de tiempo de ejecución en lugar de a optimizar la respuesta en tiempo de compilación.

Ahora bien, si se llama a esa función desde otra función con números codificados:

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

En esta función que llama el compilador puede elegir para calcular la respuesta y simplemente colocarlo allí en tiempo de compilación. Si la función myfun() es global (no está declarada como estática) el compilador no sabe si algún otro código que se va a vincular posteriormente lo usará, por lo que incluso cerca del punto de llamada en este archivo optimiza una respuesta que todavía tiene que producir la función real y dejarlo en el objeto para que llame otro código en otros archivos, por lo que aún puede examinar lo que el compilador/optimizador hace con ese código en C. A menos que utilice llvm, por ejemplo, donde puede optimizar todo el proyecto (a través de archivos), el código externo que llama a esta función utilizará la función real y no una respuesta calculada en tiempo de compilación.

Tanto gcc como clang hicieron lo que describo, dejó el código de tiempo de ejecución para la función como función global, pero dentro del archivo calculó la respuesta en tiempo de compilación y colocó la respuesta codificada en el código en lugar de llamar a la función:

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

en otra función en el mismo archivo:

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

la propia función se implementa en el código:

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 

pero donde se llama han hardcoded la respuesta porque tenían toda la información necesaria para hacerlo:

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

Si no quieres la respuesta codificada es necesario utilizar una función que no está en el mismo pase de optimización.

La manipulación del compilador y el optimizador se reduce a mucha práctica y no es una ciencia exacta ya que los compiladores y optimizadores están en constante evolución (para bien o para mal).
Al aislar un pequeño bit de código en una función, está causando problemas de otra manera, es más probable que las funciones más grandes necesiten un marco de pila y desalojen las variables de los registros a la pila, las funciones más pequeñas pueden no necesitar hacer eso y los optimizadores pueden cambiar cómo se implementa el código como resultado. Probarás el fragmento de código de una manera para ver qué está haciendo el compilador, luego lo usarás en una función más grande y no obtendrás el resultado que deseas. Si hay una instrucción exacta o una secuencia de instrucciones que desea implementar ... impleméntelas en ensamblador. Si estaba apuntando a un conjunto específico de instrucciones en un conjunto de instrucciones/procesador específico, entonces evite el juego, evite que su código cambie cuando cambie las computadoras/compiladores/etc, y simplemente use el ensamblador para ese objetivo. si es necesario ifdef o use opciones de compilación condicional para construir para diferentes objetivos sin el ensamblador.

+0

Utilicé el compilador gcc actual conocido como codesourcery y clang de la versión 3.0 de llvm. –

+2

Parece que estaba equivocado. CodeWarrior no parece estar usando GCC. Hace referencia a un ejecutable llamado mwccarm, que parece ser un compilador específico de Freescale. He intentado definir la multiplicación en una función y todavía no puedo hacer que el compilador use el comando smull; de hecho, el comando umull todavía está allí. Intentaré descargar gcc o clang y veré qué sucede. – EpicAdv

+1

No creo que le esté diciendo al compilador con éxito que compile una función independiente. Parece mirar atrás hacia dónde (y si) se llama y se optimiza. ¿Cómo puedo obligarlo a compilar la función genérica como lo está haciendo aquí? – EpicAdv

-1

GCC soporta tipos de punto fijo reales: http://gcc.gnu.org/onlinedocs/gcc/Fixed_002dPoint.html

No estoy seguro de qué instrucción se va a utilizar, pero puede hacerte la vida más fácil.

+1

Desde la página vinculada: "No todos los destinos admiten tipos de punto fijo". En mi experiencia, muy pocos objetivos admiten tipos de punto fijo. –

Cuestiones relacionadas