Contador de ciclos de clock en Cortex-M

475 views
Skip to first unread message

Pablo Ridolfi

unread,
Sep 13, 2013, 3:28:59 PM9/13/13
to embeb...@googlegroups.com
Hola, qué tal?

Luego de un tiempo de trabajar con Cortex-M3 y M4(F) me vi en la necesidad de medir tiempos de ejecución, o tiempos entre interrupciones, o bien cualquier tipo de tiempo en forma precisa, al menos con la precisión que me permita el clock del sistema.
Viendo que LPCXpresso tiene un contador de ciclos de clock en una de sus vistas durante el debugging, me puse a buscar de dónde saca esa información, si es que utiliza algún registro del sistema de debug del CPU, para poder extrapolar esto a cualquier Cortex-M y a cualquier plataforma de desarrollo.

Bueno, acá está: Hay que usar un registro que se llama Cycle Count Register, que es parte de la DWT (Data Watchpoint and Trace Unit).
Para eso en el firmware definen un par de punteros al registro de control y al contador:
volatile uint32_t * DWT_CTRL = (uint32_t *)0xE0001000;
volatile uint32_t * DWT_CYCCNT = (uint32_t *)0xE0001004;
Luego hay que activar el contador con el bit 0 del registro de control de la DWT y ponerlo en cero:

*DWT_CTRL |= 1;
*DWT_CYCCNT = 0;
Y listo. Cuando pongan un breakpoint o halteen el procesador, analicen el contenido de *DWT_CYCCNT para ver cuántos ciclos de clock pasaron hasta ese momento, o si tienen un visor de expresiones en su IDE pueden escribir algo como (float)*DWT_CYCCNT/(float)SystemCoreClock para ver directamente el tiempo en segundos. Luego le escriben cero al contador (*DWT_CYCCNT = 0;) por si necesitan reiniciarlo.

Espero que les resulte útil.
Saludos,
Pablo





Ricardo Malerba

unread,
Sep 13, 2013, 3:31:42 PM9/13/13
to embeb...@googlegroups.com
+ 1


--
-- Recibiste este mensaje porque estás suscripto al Grupo Google Embebidos32. Para postear en este grupo, escribe un email a embeb...@googlegroups.com. Para des-suscribirte, envía un email a embebidos32...@googlegroups.com. Para más opciones, visita el sitio del grupo en https://groups.google.com/d/forum/embebidos32?hl=es
---
Has recibido este mensaje porque estás suscrito al grupo "Embebidos32" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus correos electrónicos, envía un correo electrónico a embebidos32...@googlegroups.com.
Para obtener más opciones, visita https://groups.google.com/groups/opt_out.



--
Ricardo Malerba

Juan Manuel Cruz

unread,
Sep 13, 2013, 3:55:34 PM9/13/13
to embeb...@googlegroups.com
Pablo:

        Ezequiel Espósito escribió unas funciones para medir tiempos de ejecución, saludos.


        Juan

Pablo Ridolfi

unread,
Sep 13, 2013, 4:29:27 PM9/13/13
to embeb...@googlegroups.com
Hola Juan,

Hasta donde tengo entendido, Eze está definiendo el tipo de licencia con el que va a publicar su librería, por lo que no sé si las funciones que mencionás son de libre acceso. Pero por ahí ya son públicas, vos tenés alguna novedad?

Saludos!


Alfonso Maschio

unread,
Sep 13, 2013, 6:59:21 PM9/13/13
to embeb...@googlegroups.com
Hola a todos. Vengo a hacer un humilde aporte al hilo ya que tuve que hacer algo parecido pero desde un Linux.
Les dejo un driver que lee el registro que lleva la cuenta de ciclos de un Cortex A9. Es muy básico pero por ahí alguien lo necesita para hacer mediciones precisas o sacar alguna idea.
Saludos.



2013/9/13 Pablo Ridolfi <pablor...@gmail.com>



--
Maschio, Alfonso
contador_ciclos.c

Alejandro Furfaro

unread,
Sep 16, 2013, 7:27:14 PM9/16/13
to embeb...@googlegroups.com
Hola Pablo
Re piola para hacer mediciones comparativas de performance en algoritmos. Algo como el Time Stamp Counter de los procesadores de Intel.
Muy buen dato
Saludos
Alejandro Furfaro
skype: alefurfaro
linkedin: http://ar.linkedin.com/in/afurfaro
--

Alejandro Furfaro

unread,
Sep 17, 2013, 7:57:28 AM9/17/13
to embeb...@googlegroups.com
Hola Alfonso.

Usar la función POSIX read () para acceder a un char device, y obtener los ciclos de clock acumulados en un timer, es de esas cosas super útiles, y simplificadoras de código que solo se pueden encontrar en un sistema posix. Y con menos de 150 líneas de código. Dios salve a Linux. Muchas gracias por compartirlo.

Respecto del procesador, y considerando su organización, tengo una duda:

Viendo el mail inicial del thread que mandó Pablo, en los procesadores Cortex M es seguro que para medir ciclos de clock en un algoritmo este timer es lo mas fino y exacto. Pero el A9 tiene una diferencia no menor en su organización: Ejecuta fuera de orden (OOO Excecution). Esto hace que se puedan dar los tres casos siguientes:
- la instrucción que usás para leer el timer en el punto inicial del algoritmo (MRC por lo que ví en el código que enviaste) pueda ejecutarse luego de algunas de las primeras instrucciones del algoritmo a medir,
- algunas instrucciones previas al algoritmo se terminarán ejecutando luego de la instrucción MRC de lectura del timer, 
- una combinación de ambos casos

Si esto ocurre ( lo cual es 99% probable), puede introducir errores de precisión. Y este error será tanto mas significativo cuanto mas corto es el intervalo de medición.

Ojo. Este inconveniente para medir ciclos de clock y poder estimar así la eficiencia de un algoritmo, está presente en cualquier procesador que tenga OOO Execution. Los procesadores de Intel me sacaron canas verdes en su momento, pero finalmente encontré que tienen una solución utilizando la instrucción CPUID que es la única que el procesador ejecuta en orden. De este modo se puede medir con precisión cuanto tarda una instrucción si querés.

¿Alguien sabe si hay algo así en esta arquitectura?. Sería muy útil para eliminar errores de medición en algoritmos que no empleen muchos ciclos de clock.

Saludos!




Alejandro Furfaro
skype: alefurfaro
linkedin: http://ar.linkedin.com/in/afurfaro
--

Mirko Leonardo Serra Labán

unread,
Sep 17, 2013, 9:01:34 AM9/17/13
to embeb...@googlegroups.com

Habría que leer la letra chica del manual, pero yo estimo que si un lee el contador antes del CALL y después del RET el OOO no debería ser un problema (que por otro lado lo más lógico es medirlos dentro de una función, que es como más probablemente se vaya a usar).

Por otro lado el OOO solo representa un problema en funciones cortas, donde +/- 1 ciclo pesa, en funciones grandes (las que vale la pena optimizar) no sé si sea un factor decisivo, si bien hay que saber que está ahí.

De hecho, ya poner la instrucción que hace la lectura del contador podría estar modificando el tiempo de ejecución si hilamos fino.

linked-in.png
skype.png

martin ribelotta

unread,
Sep 17, 2013, 10:01:01 AM9/17/13
to embeb...@googlegroups.com
El 17 de septiembre de 2013 10:01, Mirko Leonardo Serra Labán <mirko...@gmail.com> escribió:

Habría que leer la letra chica del manual, pero yo estimo que si un lee el contador antes del CALL y después del RET el OOO no debería ser un problema (que por otro lado lo más lógico es medirlos dentro de una función, que es como más probablemente se vaya a usar).

No necesariamente... si el predictor de saltos le pega a cual va a ser la siguiente instrucción (tiene todos los datos para saberlo, como el valor del link register por ejemplo) lo mas probable es que varios ciclos antes del call/ret (por ejemplo) tengamos la/las instrucciones en cuestion en el PIPE del procesador, y si justo decidio que la carga de la instrucción es lo primero, bueno, puede que midas cualquier cosa menos lo que deberias.

Por otro lado el OOO solo representa un problema en funciones cortas, donde +/- 1 ciclo pesa, en funciones grandes (las que vale la pena optimizar) no sé si sea un factor decisivo, si bien hay que saber que está ahí.

Depende para que... Si lo que nos importa es la respuesta fina, ejemplo un PWM (ok, nadie hace un PWM con un A9, pero haganme la banca con el ejemplo) podemos terminar con un problema de jitter orrendo. Esto es extrapolable a cualquier situacion donde queramos tener tiempos precisos, no necesariamente cortos. Yo puedo querer una respuesta dentro de 40 segundos +/-10ps y para eso, la OOO presenta un verdadero probleema.

Lo que normalmente se usa en estos casos son los barrier. En la arquitectura ARMV7 (M inclusive) tenemos tres tipos:
DMB: Data Memory Barrier. Garantiza que el siguiente acceso a memoria se hara luego de que todos los accesos a memoria antes de la instrucción terminen.
DSB: Data Syncronization Barrier. Garantiza que la proxima instrucción se ejecutara luego de que todos los accesos a memoria antes de la instrucción terminen.
ISB: Instruction Syncronization Barrier. Esta es la mas heavy, flushea todo el pipe del micro obligando a buscar en cache o memoria (para limpiar la cache de los Cortex-A debemos tocar CP15 haciendo mucha magia vudu)-

Los Cortex-M4F (ARMv7M***E*** con la E bien grandota al final) tienen un predictor de saltos mas copado que los M3 a secas, lo que obliga a usar DMB/DSB/ISB dependiendo de lo que queramos hacer cuando hablamos de accesos coheretes a memoria.

Estas instrucciónes son indispensables tambien cuando hablamos de mutex/semaforos y otras yerbas de sincronización entre procesos

Saludos.
linked-in.png
skype.png

Alejandro Furfaro

unread,
Sep 17, 2013, 11:14:14 AM9/17/13
to embeb...@googlegroups.com
Hola Mirko. Tanto tiempo!
Ojo que el CALL tiene el target en la instrucción con lo cual el procesador puede adelantar el procesamiento de las primeras instrucciones de la función, ya que conoce perfectamente el destino.
De todos modos hay veces que querés tunear dentro del lazo de un for y querés robar un ciclo de clock (que en los, digamos, 100  lazos del for representara una mejora muy significativa) y no estás seguro de lo que medís, y por lo tanto como el cambio que hiciste puede afectar.
En muchos casos ni me caliento ya que la diferencia es despreciable si el algoritmo consume bastantes ciclos pero igual se nota.

Salu2


Alejandro Furfaro
skype: alefurfaro
linkedin: http://ar.linkedin.com/in/afurfaro
--

Alejandro Furfaro

unread,
Sep 17, 2013, 11:17:48 AM9/17/13
to embeb...@googlegroups.com
On 17/09/13 11:01, martin ribelotta wrote:
Depende para que... Si lo que nos importa es la respuesta fina, ejemplo un PWM (ok, nadie hace un PWM con un A9, pero haganme la banca con el ejemplo) podemos terminar con un problema de jitter orrendo. Esto es extrapolable a cualquier situacion donde queramos tener tiempos precisos, no necesariamente cortos. Yo puedo querer una respuesta dentro de 40 segundos +/-10ps y para eso, la OOO presenta un verdadero probleema.
jeje. Seguro que un PWM stand alone no. Pero en medio de una aplicación mas compleja no me jugaría a decir que nunca me lo encontraría. Nos ha tocado resolver cada engendro alguna vez.... nunca se sabe...
Salu2

Pablo Ridolfi

unread,
Sep 17, 2013, 11:16:02 AM9/17/13
to embeb...@googlegroups.com
Buenas,
 
Para Cortex-A encontré lo siguiente, quizás les sirva. Esto se tiene que ejecutar en modo kernel para habilitar el acceso al counter en modo user:
 

/* enable user-mode access to the performance counter*/

asm ("MCR p15, 0, %0, C9, C14, 0\n\t" :: "r"(1));

/* disable counter overflow interrupts (just in case)*/

asm ("MCR p15, 0, %0, C9, C14, 2\n\t" :: "r"(0x8000000f));

 
Después con una función como esta se lee el counter:

static inline unsigned int get_cyclecount (void)

{

unsigned int value;

// Read CCNT Register

asm volatile ("MRC p15, 0, %0, c9, c13, 0\t\n": "=r"(value));

return value;

}

 
Saludos!
 
 

Alejandro Furfaro

unread,
Sep 17, 2013, 12:02:24 PM9/17/13
to embeb...@googlegroups.com
Es justo el código que mandó Alfonso
El punto es como asegurar que get_cyclecount se ejecute " en orden "

Salu2

Alejandro Furfaro
skype: alefurfaro
linkedin: http://ar.linkedin.com/in/afurfaro
--

martin ribelotta

unread,
Sep 17, 2013, 12:07:44 PM9/17/13
to embeb...@googlegroups.com
El 17 de septiembre de 2013 13:02, Alejandro Furfaro <afur...@gmail.com> escribió:
Es justo el código que mandó Alfonso
El punto es como asegurar que get_cyclecount se ejecute " en orden "

La mejor (bueno, la mas salvaje de todas) es usar DSB seguido de ISB para asegurar primero, que todos los accesos a memoria esten completos y que todas las instrucciones se terminen de ejecutar. Ya que el tiempo fijo de ejecución de DSB e ISB es conocido (tiempo de emición y ejecucion + el tiempo que tarden las otras instrucciones) el overhead agregado deberia ser de solo dos ciclos mas, pero habria que probarlo en la realidad a eso...
 
linked-in.png
skype.png

Christian N

unread,
Sep 20, 2013, 11:15:15 AM9/20/13
to embeb...@googlegroups.com
Hola Alejandro

En la familia Cortex-M, no hay informacion oficial sobre instrucciones que permitan ejecutar en orden. En foros no oficiales hay algo de informacion, pero no se cuan confiable es y ya sabras cuan dificil es poder demostrar si la implementacion es correcta o no.

Para las familias A9, A11, una de las instrucciones que "serializan el pipeline" es SETEND.
Para lo que se quiere hacer se debe tener en cuenta que hay que inhabilitar/prevenir cualquier fuente de bifurcacion, (excepcion/interrupcion), ya que al modificar el orden de los bits, la ejecucion no prevista seguramente reiniciare al uP.

Atte

Leo

unread,
Oct 1, 2013, 10:48:30 AM10/1/13
to embeb...@googlegroups.com
Hola, cómo andan?
Estuve usando esta forma que propone Pablo, me vino muy bien la sugerencia.
En particular estoy midiendo cada cuanto entro a una interrupción (en modo debug). La interrupción la tengo configurada para 160 µs y con esto estoy midiendo cualquier otra cosa. Entonces me queda la duda si estoy configurando mal la interrupción del timer, si el debug me esta "mintiendo" con los tiempos o si se me esta pasando algo de largo. Alguno se peleó con esto?. Creo que voy a terminar volviendo a mi pulso en un pin de salida y colgarme con el osciloscopio :s
Saludos!

Pablo Ridolfi

unread,
Oct 1, 2013, 11:06:13 AM10/1/13
to embeb...@googlegroups.com
Hola Leandro, podés mandar el pedazo de código que estás evaluando?



Leo

unread,
Oct 1, 2013, 4:11:52 PM10/1/13
to embeb...@googlegroups.com
Va el código:

La ISR del timer es:

void TIMER0_IRQHandler (void)
{
        /*  Clear Interrupt */
        TIM_ClearIntPending(LPC_TIM0,TIM_MR0_INT);

        elapsed=(float)*DWT_CYCCNT/(float)SystemCoreClock; 
       *DWT_CYCCNT = 0;
// printf("%e\n",elapsed);

        col++;
        col%=(COLUMNAS+1);
        if(!col)
        {
        row++;
        row%=FILAS;
        SetChanRow(row);
        }

        SetChanCol(col);

        return;
}
Si ejecuto la linea con el printf (sacando la info por consola) los tiempos obviamente cambian, pero en vez de medirme más tiempo me mide menos tiempo :s

La configuración del timer (acá le podría estar pifiando, aunque esta fuertemente "inspirada" en http://www.coocox.org/show_exam/TIMER/204.html)

void Timer0_init()
{

        TIM_TIMERCFG_Type TMR0_Cfg;
        TIM_MATCHCFG_Type TMR0_Match;

        /* On reset, Timer0/1 are enabled (PCTIM0/1 = 1), and Timer2/3 are disabled (PCTIM2/3 = 0).*/
        /* Initialize timer 0, prescale count time of 100uS */
        TMR0_Cfg.PrescaleOption = TIM_PRESCALE_USVAL;
        //TMR0_Cfg.PrescaleValue = 100;
        TMR0_Cfg.PrescaleValue = 10;
        /* Use channel 0, MR0 */
        TMR0_Match.MatchChannel = 0;
        /* Enable interrupt when MR0 matches the value in TC register */
        TMR0_Match.IntOnMatch = ENABLE;
        /* Enable reset on MR0: TIMER will reset if MR0 matches it */
        TMR0_Match.ResetOnMatch = TRUE;
        /* Don't stop on MR0 if MR0 matches it*/
        TMR0_Match.StopOnMatch = FALSE;
        /* Do nothing for external output pin if match (see cmsis help, there are another options) */
        TMR0_Match.ExtMatchOutputType = TIM_EXTMATCH_NOTHING;
        /* Set Match value, count value of 10000 (10000 * 100uS = 1000000us = 1s --> 1 Hz) */
        //TMR0_Match.MatchValue = 10000;
        TMR0_Match.MatchValue = 16;
        /* Set configuration for Tim_config and Tim_MatchConfig */
        TIM_Init(LPC_TIM0, TIM_TIMER_MODE, &TMR0_Cfg);
        TIM_ConfigMatch(LPC_TIM0, &TMR0_Match);

        /* Preemption = 1, sub-priority = 1 */
        NVIC_SetPriority(TIMER0_IRQn, ((0x01<<3)|0x01));
        /* Enable interrupt for timer 0 */
        NVIC_EnableIRQ(TIMER0_IRQn);
//        /* Start timer 0 */
//        TIM_Cmd(LPC_TIM0, ENABLE);
}

Mas adelante cuando termino de configurar el hard prendo en timer. Ahora lo loco de todo esto es: la interrupción está en 160µs pero cuando veo la variable elapsed el tiempo es del orden de los 10µs.

Alguna idea?
Saludos!

JG

unread,
Oct 2, 2013, 11:30:04 AM10/2/13
to embeb...@googlegroups.com
No estarás teniendo un overflow del registro y por eso creés que tarda "menos" ?



El viernes, 13 de septiembre de 2013 16:28:59 UTC-3, Pablo Ridolfi escribió:
Reply all
Reply to author
Forward
0 new messages