En una aplicación en tiempo real¹ en un ARM Cortex M3 (similar a STM32F101), necesito sondear un poco del registro de un periférico interno hasta que sea cero, en un bucle lo más ajustado posible. Utilizo bandas de bits para acceder al bit apropiado. El código C (de trabajo) es
while (*(volatile uint32_t*)kMyBit != 0);
Ese código se copia en la RAM ejecutable en el chip. Después de una optimización manual², el ciclo de sondeo se reduce a lo siguiente, que cronometré³ a 6 ciclos:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 D1FC BNE 0x00600200
¿Cómo se puede reducir la incertidumbre del sondeo? Un ciclo de 5 ciclos encajaría en mi objetivo: muestrear el mismo bit lo más cerca posible a 15.5 ciclos después de que se puso a cero.
Mi especificación requiere la detección confiable de un pulso bajo al menos 6.5 ciclos de reloj de la CPU; clasificándolo confiablemente como corto si dura menos de 12.5 ciclos; y clasificarlo de manera confiable siempre que dure más de 18.5 ciclos. Los pulsos no tienen una relación de fase definida con el reloj de la CPU, que es mi única referencia de sincronización precisa. Eso requiere un bucle de sondeo de 5 relojes como máximo. En realidad, estoy emulando código que se ejecutó en una CPU de 8 bits de hace décadas que podría sondear con un ciclo de 5 relojes, y lo que hizo se ha convertido en la especificación.
Traté de compensar la alineación del código insertando NOP antes del ciclo, en las muchas variantes que probé, pero nunca observé un cambio.
Traté de invertir el CMP y LDR, pero aún obtengo 6 ciclos:
0x00600200 681A LDR r2,[r3,#0x00]
; we loop here
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FC BNE 0x00600202
Este es de 8 ciclos.
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 681A LDR r2,[r3,#0x00]
0x00600204 2A00 CMP r2,#0x00
0x00600206 D1FB BNE 0x00600200
Pero este es de 9 ciclos:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FB BNE 0x00600200
¹ Medir cuánto tiempo el bit es bajo, en un contexto donde no se produce ninguna interrupción.
² El código inicial generado por el compilador utilizó r12 como el registro de destino, y eso agregó 4 bytes de código al bucle, lo que costó 1 ciclo.
³ Los números proporcionados se obtienen con un emulador STIce en tiempo real supuestamente preciso en el ciclo y su función de activación del emulador en la lectura en la dirección del registro. Anteriormente probé el contador "Estados" con un punto de interrupción en el bucle, pero el resultado depende de la ubicación del punto de interrupción. Un solo paso es aún peor: siempre da 4 ciclos para LDR, cuando eso es al menos en algún momento hasta 3.
gcc -Os -mcpu=cortex-m3
?
ldr
/ cbz reg, end_of_loop
para los internos, y aún un cmp
/ bnz
en la parte inferior. Pero eso le daría un intervalo de sondeo no uniforme, por ejemplo, 1 de cada 8 sondeos, en caso de que sea importante.