Recuento de ciclos con CPU modernas (por ejemplo, ARM)

14

En muchas aplicaciones, una CPU cuya ejecución de instrucción tiene una relación de tiempo conocida con estímulos de entrada esperados puede manejar tareas que requerirían una CPU mucho más rápida si la relación fuera desconocida. Por ejemplo, en un proyecto que hice usando un PSOC para generar video, usé código para generar un byte de datos de video cada 16 relojes de CPU. Como probar si el dispositivo SPI está listo y bifurcarse si no, IIRC tomaría 13 relojes, y una carga y almacenamiento de datos de salida tomaría 11, no había forma de probar la disponibilidad del dispositivo entre bytes; en cambio, simplemente arreglé para que el procesador ejecutara exactamente 16 ciclos de código para cada byte después del primero (creo que usé una carga indexada real, una carga indexada ficticia y una tienda). La primera escritura de SPI de cada línea ocurrió antes del inicio del video, y para cada escritura posterior había una ventana de 16 ciclos donde la escritura podía ocurrir sin desbordamiento o desbordamiento del búfer. El bucle de ramificación generó una ventana de incertidumbre de 13 ciclos, pero la ejecución predecible de 16 ciclos significó que la incertidumbre para todos los bytes subsiguientes encajaría en la misma ventana de 13 ciclos (que a su vez cabe dentro de la ventana de 16 ciclos de cuándo la escritura podría aceptablemente ocurrir).

Para las CPU más antiguas, la información de sincronización de instrucciones era clara, disponible y sin ambigüedades. Para los ARM más nuevos, la información de temporización parece mucho más vaga. Entiendo que cuando el código se ejecuta desde flash, el comportamiento de almacenamiento en caché puede hacer que las cosas sean mucho más difíciles de predecir, por lo que esperaría que cualquier código contado por ciclo se ejecute desde la RAM. Sin embargo, incluso cuando se ejecuta código desde RAM, las especificaciones parecen un poco vagas. ¿Sigue siendo una buena idea el uso de código contado por ciclo? Si es así, ¿cuáles son las mejores técnicas para que funcione de manera confiable? ¿Hasta qué punto se puede suponer con seguridad que un proveedor de chips no va a introducir silenciosamente un "nuevo chip mejorado" que afeita un ciclo a la ejecución de ciertas instrucciones en ciertos casos?

Suponiendo que el siguiente ciclo comienza en un límite de palabra, ¿cómo se determinaría en función de las especificaciones exactamente cuánto tiempo tomaría (suponga que Cortex-M3 con memoria de estado de espera cero; nada más sobre el sistema debería importar para este ejemplo).

myloop:
  mov r0, r0; Instrucciones simples y cortas para permitir que se obtengan más instrucciones
  mov r0, r0; Instrucciones simples y cortas para permitir que se obtengan más instrucciones
  mov r0, r0; Instrucciones simples y cortas para permitir que se obtengan más instrucciones
  mov r0, r0; Instrucciones simples y cortas para permitir que se obtengan más instrucciones
  mov r0, r0; Instrucciones simples y cortas para permitir que se obtengan más instrucciones
  mov r0, r0; Instrucciones simples y cortas para permitir que se obtengan más instrucciones
  agrega r2, r1, # 0x12000000; Instrucción de 2 palabras
  ; Repita lo siguiente, posiblemente con diferentes operandos
  ; Seguirá agregando valores hasta que ocurra un acarreo
  itcc
  agregacc r2, r2, # 0x12000000; Instrucción de 2 palabras, más "palabra" adicional para itcc
  itcc
  agregacc r2, r2, # 0x12000000; Instrucción de 2 palabras, más "palabra" adicional para itcc
  itcc
  agregacc r2, r2, # 0x12000000; Instrucción de 2 palabras, más "palabra" adicional para itcc
  itcc
  agregacc r2, r2, # 0x12000000; Instrucción de 2 palabras, más "palabra" adicional para itcc
; ... etc, con instrucciones de dos palabras más condicionales
  sub r8, r8, # 1
  bpl myloop

Durante la ejecución de las primeras seis instrucciones, el núcleo tendría tiempo para buscar seis palabras, de las cuales tres se ejecutarían, por lo que podría haber hasta tres pretraídas. Las siguientes instrucciones son las tres palabras cada una, por lo que no sería posible que el núcleo obtenga instrucciones tan rápido como se ejecutan. Esperaría que algunas de las instrucciones "it" tomaran un ciclo, pero no sé cómo predecir cuáles.

Sería bueno si ARM pudiera especificar ciertas condiciones bajo las cuales el tiempo de instrucción "it" sería determinista (por ejemplo, si no hay estados de espera o contención de bus de código, y las dos instrucciones anteriores son instrucciones de registro de 16 bits, etc.) pero no he visto ninguna de esas especificaciones.

Aplicación de muestra

Supongamos que uno está tratando de diseñar una placa secundaria para un Atari 2600 para generar salida de video componente a 480P. El 2600 tiene un reloj de píxeles de 3.579MHz y un reloj de CPU de 1.19MHz (dot clock / 3). Para el video componente 480P, cada línea debe emitirse dos veces, lo que implica una salida de reloj de puntos de 7.158MHz. Debido a que el chip de video (TIA) de Atari emite uno de los 128 colores usando una señal luma de 3 bits más una señal de fase con una resolución de aproximadamente 18 ns, sería difícil determinar con precisión el color con solo mirar las salidas. Un mejor enfoque sería interceptar las escrituras en los registros de color, observar los valores escritos y alimentar cada registro en los valores de luminancia TIA correspondientes al número de registro.

Todo esto se podría hacer con un FPGA, pero algunos dispositivos ARM bastante rápidos se pueden obtener mucho más baratos que un FPGA con suficiente RAM para manejar el almacenamiento en búfer necesario (sí, sé que para los volúmenes que tal cosa podría producirse, el costo no es ' t un factor real). Sin embargo, requerir que el ARM mire la señal del reloj entrante aumentaría significativamente la velocidad de CPU requerida. El recuento de ciclos predecible podría hacer las cosas más limpias.

Un enfoque de diseño relativamente simple sería hacer que un CPLD mire la CPU y el TIA y genere una señal de sincronización RGB + de 13 bits, y luego haga que ARM DMA tome valores de 16 bits de un puerto y los escriba en otro con el tiempo adecuado. Sin embargo, sería un desafío de diseño interesante ver si una ARM barata podría hacer todo. DMA podría ser un aspecto útil de un enfoque todo en uno si se pudieran predecir sus efectos en los recuentos de ciclos de la CPU (especialmente si los ciclos de DMA podrían ocurrir en ciclos cuando el bus de memoria estaba inactivo), pero en algún momento del proceso el ARM tendría que realizar sus funciones de búsqueda de tablas y observación de buses. Tenga en cuenta que, a diferencia de muchas arquitecturas de video en las que los registros de color se escriben durante los intervalos de supresión, el Atari 2600 escribe frecuentemente en los registros de color durante la parte mostrada de un cuadro,

Quizás el mejor enfoque sería usar un par de chips de lógica discreta para identificar escrituras en color y forzar los bits más bajos de los registros de color a los valores adecuados, y luego usar dos canales DMA para muestrear el bus de CPU entrante y los datos de salida TIA, y un tercer canal DMA para generar los datos de salida. La CPU sería libre de procesar todos los datos de ambas fuentes para cada línea de exploración, realizar la traducción necesaria y almacenarla en el búfer para la salida. El único aspecto de las tareas del adaptador que tendría que suceder en "tiempo real" sería la anulación de los datos escritos en COLUxx, y eso podría solucionarse utilizando dos chips lógicos comunes.

Super gato
fuente

Respuestas:

7

Yo voto por DMA. Es realmente flexible en Cortex-M3 y versiones posteriores, y puede hacer todo tipo de locuras como obtener datos automáticamente de un lugar y enviarlos a otro con una velocidad especificada o en algunos eventos sin gastar NINGÚN ciclo de CPU. DMA es mucho más confiable.

Pero puede ser bastante difícil de entender en detalles.

Otra opción son los núcleos blandos en FPGA con implementación de hardware de estas cosas apretadas.

BarsMonster
fuente
1
Me gusta la noción de DMA. Sin embargo, no creo que el núcleo Cortex M3 tenga DMA, eso es una función de los chips de los fabricantes individuales, y todos parecen implementarlo de manera diferente. Una cosa que me parece molesta con al menos la única implementación con la que realmente he jugado (STM32L152), es que no puedo encontrar ninguna manera de tener un flash estroboscópico cuando se emiten datos DMA. Tampoco está claro qué factores pueden afectar la puntualidad de DMA.
supercat
1
En cualquier caso, con respecto a una de las primeras aplicaciones que estaba considerando para un ciclo preciso, publiqué más información en la pregunta original. Tengo curiosidad por lo que piensas. Otra situación en la que estaba reflexionando sobre los golpes de ciclo sería enviar los datos de la pantalla a una pantalla LCD en color. Los datos se almacenarían en la memoria RAM utilizando colores de 8 bits, pero la pantalla necesita colores de 16 bits. La forma más rápida en la que pensé para generar datos habría sido usar hardware para generar las luces estroboscópicas de escritura, por lo que la CPU solo tendría que desconectar los datos. ¿Sería bueno traducir 8-> 16 bits en un pequeño búfer ...
supercat
1
... y luego organizar DMA para transferir eso, o ¿cuál sería el mejor enfoque?
supercat
4

La información de tiempo está disponible, pero, como usted señaló, en ocasiones puede ser vaga. Hay mucha información sobre el tiempo en la Sección 18.2 y la Tabla 18.1 del Manual de referencia técnica para el Cortex-M3, por ejemplo ( pdf aquí ) y un extracto aquí:

extracto de 18.2

que dan una lista de condiciones para el tiempo máximo. El tiempo para muchas instrucciones depende de factores externos, algunos de los cuales dejan ambigüedades. He resaltado cada una de las ambigüedades que encontré en el siguiente extracto de esa sección:

[1] Las ramas toman un ciclo para la instrucción y luego la recarga de la tubería para la instrucción de destino. Las ramas no tomadas son 1 ciclo total. Las ramas tomadas con una inmediata son normalmente 1 ciclo de recarga de tubería (2 ciclos en total). Las ramas tomadas con operando de registro son normalmente 2 ciclos de recarga de tubería (3 ciclos en total). La recarga de la tubería es más larga [¿Cuánto más?] Cuando se ramifica a instrucciones de 32 bits sin alinear, además de los accesos a una memoria más lenta. Se emite una sugerencia de rama al bus de código que permite un sistema más lento [¿Cuánto más lento?] Se precargue. Esto puede [¿Es esto opcional?] Reducir [¿En cuánto?] La penalización del objetivo de la rama para una memoria más lenta, pero nunca menos de lo que se muestra aquí.

[2] En general, las instrucciones de almacenamiento de carga toman dos ciclos para el primer acceso y un ciclo para cada acceso adicional. Las tiendas con compensaciones inmediatas toman un ciclo.

[3] UMULL / SMULL / UMLAL / SMLAL usa terminación temprana dependiendo del tamaño de los valores de origen [¿Qué tamaños?]. Estos son interrumpibles (abandonados / reiniciados), con la peor latencia de un ciclo. Las versiones MLAL toman cuatro a siete ciclos y las versiones MULL toman de tres a cinco ciclos . Para MLAL, la versión firmada es un ciclo más larga que la sin firmar.

[4] Las instrucciones de TI se pueden plegar . [¿Cuando? Ver comentarios.]

[5] Los tiempos de DIV dependen del dividendo y el divisor . [Mismo problema que MUL] DIV es interrumpible (abandonado / reiniciado), con la peor latencia de un ciclo. Cuando el dividendo y el divisor son similares [¿Qué tan similar?] En tamaño, la división termina rápidamente. El tiempo mínimo es para casos de divisor mayor que dividendo y divisor de cero. Un divisor de cero devuelve cero (no es un error), aunque hay una trampa de depuración disponible para detectar este caso. [¿Cuáles son los rangos que se dieron para MUL?]

[6] El sueño es un ciclo para la instrucción más tantos ciclos de sueño como sea apropiado. WFE solo usa un ciclo cuando el evento ha pasado. WFI es normalmente más de un ciclo a menos que una interrupción se suspenda exactamente al ingresar a WFI.

[7] ISB toma un ciclo (actúa como rama). DMB y DSB toman un ciclo a menos que los datos estén pendientes en el búfer de escritura o LSU. Si entra una interrupción durante una barrera, se abandona / reinicia.

Para todos los casos de uso, será más complejo que el conteo "Esta instrucción es un ciclo, esta instrucción es dos ciclos, este es un ciclo ..." posible en procesadores más simples, más lentos y más antiguos. Para algunos casos de uso, no encontrará ambigüedades. Si encuentra ambigüedades, le sugiero:

  1. Póngase en contacto con su proveedor y pregúnteles cuál es el tiempo de instrucción para su caso de uso.
  2. Prueba para especificar el comportamiento ambiguo
  3. Vuelva a probar cualquier revisión del procesador y especialmente cuando esté pasando por los cambios del proveedor.

Estos requisitos probablemente respondan a su pregunta: "No, no es una buena idea, a menos que las dificultades encontradas valgan la pena", pero eso ya lo sabía.

Kevin Vermeer
fuente
1
Consideraría que lo siguiente es vago: "La recarga de la tubería es más larga cuando se ramifica a instrucciones de 32 bits no alineadas además de accesos a una memoria más lenta" no dice si agrega precisamente un ciclo, y "no se pueden plegar las instrucciones de TI" No especifique en qué condiciones serán o no serán.
supercat
1
El momento "IT" parecería especialmente problemático, ya que esa es una instrucción que a menudo se usaría dentro de un ciclo cerrado de recuento de ciclos, y estoy bastante seguro de que no siempre se puede plegar. Supongo que si uno siempre se bifurca al inicio de un bucle sensible al tiempo, obliga al bucle a comenzar en un límite de palabra, evita cualquier carga condicional o almacena dentro del bucle, y uno no pone ninguna instrucción "IT" inmediatamente después de cargar o actualizar la tienda de registro, los tiempos de "TI" serían consistentes, pero la especificación no lo deja claro.
supercat
1
Supongo que el IT probablemente podría (sinceramente) notar algo como: "En ausencia de estados de espera o contención del bus de código, el plegado del IT está garantizado si (1) la instrucción anterior era una instrucción de 16 bits que no tenía acceso memoria o el contador del programa, y ​​(2) o la siguiente instrucción es una instrucción de 16 bits, o la instrucción anterior no era el objetivo de una rama "no alineada". El plegado de TI también puede ocurrir en otras circunstancias no especificadas ". Tal especificación permitiría escribir programas con tiempos predecibles de instrucción de TI al garantizar que el código se organizó como se indica.
supercat
1
Wow: confieso que solo había pasado por simples recuentos de ciclos en el peor de los casos, en lugar de haber luchado con las advertencias debajo de la mesa. Mi respuesta actualizada resalta algunas otras ambigüedades.
Kevin Vermeer
1
Hay muchas situaciones en las que uno está interesado en el conteo del peor de los casos, y un número justo donde uno está interesado en el conteo del mejor de los casos (por ejemplo, si un puerto SPI puede generar un byte cada 16 ciclos, generar cada byte tomaría 14 ciclos en el mejor de los casos, y la verificación de la preparación tomaría 5 ciclos, la verificación de la preparación de cada byte limitaría la velocidad a un byte cada 19 ciclos en el mejor de los casos; escribir a ciegas con dos NOP adicionales permitiría una velocidad de un byte cada 16 ciclos en el mejor de los casos ) Los casos en los que se necesita un tiempo preciso no son tan comunes, pero pueden surgir.
supercat
3

Una forma de solucionar este problema es utilizar dispositivos con tiempos deterministas o predecibles, como los chips Parallax Propeller y XMOS:

http://www.parallaxsemiconductor.com/multicoreconcept

http://www.xmos.com/

El conteo de ciclos funciona muy bien con el Propeller (se debe usar lenguaje ensamblador), mientras que los dispositivos XMOS tienen una utilidad de software muy poderosa, el Analizador de sincronización XMOS, que funciona con aplicaciones escritas en el lenguaje de programación XC:

https://www.xmos.com/download/public/XMOS-Timing-Analyzer-Whitepaper%281%29.pdf

Leon Heller
fuente
1
Estoy empezando a pensar que Leon tiene acciones en XMOS ... ;-)
Federico Russo
1
Simplemente me gustan sus chips y las personas que trabajan allí. Parallax es una buena compañía con buenos productos también.
Leon Heller
1
Sí, no te ofendas. Simplemente me sorprende que todas las respuestas (excepto una) donde se menciona XMOS sean de usted. No hay nada de malo en entusiasmarse con algo.
Federico Russo el
@Federico, @Leon: eso es exactamente lo que me preocupa un poco de XMOS: ¿por qué hay solo 1 usuario en el mundo (al menos así es)? Si es tan genial, ¿por qué no se habla de la ciudad? Nunca escuché a nadie hablar de eso, menos usarlo.
stevenvh
Pruebe los foros de XMOS: xcore.com
Leon Heller
2

El conteo de ciclos se vuelve más problemático a medida que se aleja de los microcontroladores de bajo nivel y se pasa a procesadores informáticos de uso más general. El primero generalmente tiene un tiempo de instrucción bien especificado, en parte por las razones por las que se encuentra. También se debe a que su arquitectura es bastante simple, por lo que los tiempos de instrucción son fijos y reconocibles.

Un buen ejemplo de esto son la mayoría de los PIC de Microchip. Las series 10, 12, 16 y 18 tienen un tiempo de instrucción muy bien documentado y predecible. Esta puede ser una característica útil en el tipo de pequeñas aplicaciones de control para las que están destinados estos chips.

A medida que se aleja del costo ultra bajo y, por lo tanto, el diseñador puede gastar más área de chips para obtener una mayor velocidad de una arquitectura más exótica, también se aleja de la previsibilidad. Eche un vistazo a las variantes modernas de x86 como ejemplos extremos de esto. Hay varios niveles de cachés, vitualización de la memoria, búsqueda anticipada, canalización y más, lo que hace que sea casi imposible contar los ciclos de instrucción. Sin embargo, en esta aplicación no importa, ya que el cliente está interesado en la alta velocidad, no en la previsibilidad del tiempo de instrucción.

Incluso puede ver este efecto en funcionamiento en modelos Microchip superiores. El núcleo de 24 bits (series 24, 30 y 33) tiene un tiempo de instrucción en gran medida predecible, excepto por algunas excepciones cuando hay contenciones de bus de registro. Por ejemplo, en algunos casos, la máquina inserta un bloqueo cuando la siguiente instrucción usa un registro con algunos modos de direccionamiento indirecto cuyo valor se modificó en la instrucción anterior. Este tipo de bloqueo es inusual en un dsPIC, y la mayoría de las veces puede ignorarlo, pero muestra cómo estas cosas se arrastran debido a que los diseñadores intentan darle un procesador más rápido y más capaz.

Entonces, la respuesta básica es que eso es parte de la compensación cuando elige un procesador. Para aplicaciones de control pequeñas, puede elegir algo pequeño, barato, de baja potencia y con tiempos de instrucción predecibles. A medida que exige más potencia de procesamiento, la arquitectura cambia, por lo que debe abandonar el tiempo de instrucción predecible. Afortunadamente, eso no es un problema a medida que llega a aplicaciones de uso general y de uso intensivo de cómputo, por lo que creo que las compensaciones funcionan razonablemente bien.

Olin Lathrop
fuente
Estoy de acuerdo en que, en general, las aplicaciones que requieren más cómputo se vuelven menos sensibles a la temporización microscópica, pero hay algunos escenarios en los que uno podría necesitar un poco más de procesamiento que el PIC-18, pero también necesita previsibilidad. Me pregunto en qué medida debería esforzarme por aprender cosas como las arquitecturas PIC de 16 bits, o en qué medida debería pensar que el ARM probablemente será adecuado.
supercat
0

Sí, aún puedes hacerlo, incluso en un ARM. El mayor problema con eso en un ARM es que ARM vende núcleos, no chips, y se conoce el momento central, pero lo que el vendedor de chips envuelve varía de un proveedor a otro y, a veces, de una familia de chips a otra dentro del proveedor. Por lo tanto, un chip en particular de un proveedor en particular puede ser bastante determinista (si no usa cachés, por ejemplo), pero se vuelve más difícil de portar. Cuando se trata de 5 relojes aquí y 11 relojes allá usando temporizadores es problemático, ya que la cantidad de instrucciones que se necesitan para probar el temporizador y determinar si su tiempo de espera ha expirado. Por los sonidos de su experiencia de programación pasada, estoy dispuesto a apostar que probablemente depurará con un osciloscopio como lo hago yo, para que pueda probar un circuito cerrado en el chip a la velocidad del reloj, mirar el spi o i2c o cualquier forma de onda, agregar o eliminar los nops, cambiar el número de veces a través del ciclo y básicamente sintonizar. Al igual que con cualquier plataforma, no usar interrupciones ayuda en gran medida la naturaleza determinista de la ejecución de la instrucción.

No, no es tan simple como un PIC, pero aún es bastante factible, especialmente si el retraso / sincronización se acerca a la velocidad del reloj del procesador. Varios proveedores basados ​​en ARM le permiten multiplicar la velocidad del reloj y obtener, por ejemplo, 60 MHz de una referencia de 8 mhz, por lo que si necesita una interfaz de 2 mhz en lugar de hacer algo cada 4 instrucciones, puede aumentar el reloj (si tiene el presupuesto de energía) y luego usa un temporizador y date muchos relojes para hacer otras cosas también.

viejo contador de tiempo
fuente