En muchas aplicaciones, una CPU cuya ejecución de instrucción tiene una relación de tiempo conocida con los estímulos de entrada esperados puede manejar tareas que requerirían una CPU mucho más rápida si no se conociera la relación. 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. Dado que probar si el dispositivo SPI está listo y bifurcarse si no, IIRC tomaría 13 relojes, y cargar y almacenar datos de salida tomaría 11, no había forma de probar la preparación del dispositivo entre bytes; en cambio, simplemente arreglé que el procesador ejecutara exactamente 16 ciclos de código por cada byte después del primero (creo que usé una carga indexada real, una carga indexada ficticia y una tienda). La primera escritura SPI de cada línea ocurrió antes del inicio del video, y para cada escritura subsiguiente había una ventana de 16 ciclos en la que la escritura podía ocurrir sin saturación o subejecución del búfer. El ciclo de bifurcació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 posteriores se ajustaría a esa misma ventana de 13 ciclos (que a su vez encajaría dentro de la ventana de 16 ciclos de cuando la escritura podría aceptarse). ocurrir).
Para las CPU más antiguas, la información de temporización de las instrucciones era clara, disponible e inequívoca. Para los ARM más nuevos, la información de tiempo parece mucho más vaga. Entiendo que cuando el código se ejecuta desde flash, el comportamiento del 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 ciclos se ejecute desde la RAM. Sin embargo, incluso cuando se ejecuta código desde la RAM, las especificaciones parecen un poco vagas. ¿Sigue siendo una buena idea el uso de código contado por ciclos? 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 introducirá silenciosamente un chip "nuevo y mejorado" que elimina un ciclo de 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).
mi bucle: mov r0,r0 ; Instrucciones breves y sencillas para permitir la búsqueda previa de más instrucciones mov r0,r0 ; Instrucciones breves y sencillas para permitir la búsqueda previa de más instrucciones mov r0,r0 ; Instrucciones breves y sencillas para permitir la búsqueda previa de más instrucciones mov r0,r0 ; Instrucciones breves y sencillas para permitir la búsqueda previa de más instrucciones mov r0,r0 ; Instrucciones breves y sencillas para permitir la búsqueda previa de más instrucciones mov r0,r0 ; Instrucciones breves y sencillas para permitir la búsqueda previa de 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" extra para itcc itcc agregacc r2,r2,#0x12000000 ; Instrucción de 2 palabras, más "palabra" extra para itcc itcc agregacc r2,r2,#0x12000000 ; Instrucción de 2 palabras, más "palabra" extra para itcc itcc agregacc r2,r2,#0x12000000 ; Instrucción de 2 palabras, más "palabra" extra para itcc ;...etc, con más instrucciones condicionales de dos palabras secundario r8,r8,#1 mi bucle bpl
Durante la ejecución de las primeras seis instrucciones, el núcleo tendría tiempo para buscar seis palabras, de las cuales se ejecutarían tres, por lo que podría haber hasta tres precargadas. Las siguientes instrucciones son de tres palabras cada una, por lo que no sería posible que el núcleo obtenga las instrucciones tan rápido como se están ejecutando. Esperaría que algunas de las instrucciones "eso" 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 la 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.
Ejemplo de aplicación
Supongamos que uno está tratando de diseñar una placa secundaria para un Atari 2600 para generar una salida de video por componentes a 480P. El 2600 tiene un reloj de píxeles de 3,579 MHz y un reloj de CPU de 1,19 MHz (reloj de puntos/3). Para video componente de 480P, cada línea debe emitirse dos veces, lo que implica una salida de reloj de puntos de 7,158 MHz. Debido a que el chip de video de Atari (TIA) emite uno de los 128 colores utilizando una señal de luminancia 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 observar las salidas. Un mejor enfoque sería interceptar 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 se pueden producir, el costo es ' t un factor real). Sin embargo, requerir que el ARM mire la señal del reloj entrante aumentaría significativamente la velocidad requerida de la CPU. Los recuentos de ciclos predecibles podrían hacer las cosas más limpias.
Un enfoque de diseño relativamente simple sería hacer que un CPLD observe 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 la sincronización adecuada. Sin embargo, sería un desafío de diseño interesante ver si un ARM barato 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 pudieran ocurrir en ciclos cuando el bus de memoria estaba inactivo), pero en algún punto del proceso el ARM tendría que realizar sus funciones de búsqueda de tablas y observación de autobuses. Tenga en cuenta que, a diferencia de muchas arquitecturas de video donde los registros de color se escriben durante los intervalos de borrado, el Atari 2600 frecuentemente escribe 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 las escrituras de color y forzar los bits inferiores 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. Entonces, la CPU estaría libre para procesar todos los datos de ambas fuentes para cada línea de escaneo, realizar la traducción necesaria y almacenarlos en el búfer para la salida. El único aspecto de las funciones del adaptador que tendría que ocurrir 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.
Voto por DMA. Es realmente flexible en Cortex-M3 y superior, y puede hacer todo tipo de cosas locas, como obtener datos automáticamente de un lugar y enviarlos a otro con una velocidad específica o en algunos eventos sin gastar NINGÚN ciclo de CPU. DMA es mucho más fiable.
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 ajustadas.
La información de tiempo está disponible, pero, como usted señaló, ocasionalmente puede ser vaga. Hay mucha información de tiempo en la Sección 18.2 y la Tabla 18.1 del Manual de referencia técnica para Cortex-M3, por ejemplo, ( pdf aquí ), y un extracto aquí:
que dan una lista de condiciones para el tiempo máximo. El momento de 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 canalización se recarga para la instrucción de destino. Las sucursales no tomadas son 1 ciclo en total. Las ramas tomadas con un inmediato son normalmente 1 ciclo de recarga de tubería (2 ciclos en total). Las ramas tomadas con el 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 tiempo más?] cuando se bifurca a instrucciones de 32 bits no alineadas además de los accesos a una memoria más lenta. Se emite una sugerencia de bifurcación al bus de código que permite que un sistema más lento [¿Cuánto más lento?] se precargue. Esto puede [¿Es esto opcional?] reducir [¿Por cuánto?] la penalización del objetivo de bifurcación por una memoria más lenta, pero nunca menos de lo que se muestra aquí.
[2] Generalmente, las instrucciones de carga y almacenamiento 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 usan terminación anticipada según el tamaño de los valores de origen [¿Qué tamaños?]. Estos son interrumpibles (abandonados/reiniciados), con una latencia en el peor de los casos de un ciclo. Las versiones MLAL toman de 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 no firmada.
[4] Las instrucciones de TI se pueden plegar . [¿Cuándo? Ver comentarios.]
[5] Los tiempos de DIV dependen del dividendo y el divisor . [El mismo problema que MUL] DIV es interrumpible (abandonado/reiniciado), con una latencia en el peor de los casos de un ciclo. Cuando el dividendo y el divisor son similares [¿Qué tan similares?] 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 ocurra una interrupción pendiente exactamente al ingresar a WFI.
[7] ISB toma un ciclo (actúa como sucursal). DMB y DSB tardan un ciclo a menos que haya datos 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 "Esta instrucción es un ciclo, esta instrucción es dos ciclos, esto es un ciclo..." conteo 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:
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.
Una forma de solucionar este problema es usar dispositivos con tiempos deterministas o predecibles, como los chips Parallax Propeller y XMOS:
http://www.parallaxsemiconductor.com/multicoreconcept
El conteo de ciclos funciona muy bien con Propeller (se debe usar el lenguaje ensamblador), mientras que los dispositivos XMOS tienen una utilidad de software muy potente, el XMOS Timing Analyzer, 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
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 los procesadores informáticos de propósito más general. El primero generalmente tiene un tiempo de instrucción bien especificado, en parte por las razones de su sitio. También se debe a que su arquitectura es bastante simple, por lo que los tiempos de instrucción son fijos y se pueden conocer.
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 ultrabajo y, por lo tanto, el diseñador puede gastar más área de chip 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 x86 modernas como ejemplos extremos de esto. Hay varios niveles de cachés, virtualización de la memoria, búsqueda anticipada, canalización y más, que hacen que contar los ciclos de instrucciones sea casi imposible. 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 superiores de Microchip. El núcleo de 24 bits (series 24, 30 y 33) tiene un tiempo de instrucción predecible en gran medida, excepto 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 fue cambiado 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 deslizan debido a que los diseñadores intentan brindarle 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 eliges un procesador. Para aplicaciones de control pequeñas, puede elegir algo pequeño, económico, de bajo consumo y con un tiempo de instrucción predecible. A medida que exige más potencia de procesamiento, la arquitectura cambia, por lo que debe renunciar a un tiempo de instrucción predecible. Afortunadamente, eso es un problema menor a medida que se llega a aplicaciones de propósito general y de uso intensivo de cómputo, por lo que creo que las ventajas y desventajas funcionan razonablemente bien.
Sí, aún puede hacerlo, incluso en un ARM. El mayor problema con eso en un ARM es que ARM vende núcleos, no chips, y se conoce la sincronización del núcleo, pero lo que el proveedor de chips envuelve varía de un proveedor a otro y, a veces, de una familia de chips a otra dentro del proveedor. Entonces, 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á, el uso de 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 depure con un osciloscopio como lo hago yo, por lo que puede probar un bucle cerrado en el chip a la velocidad del reloj, mirar el spi o i2c o cualquier forma de onda, agregue o quitar nops, cambiar el número de veces a través del bucle y básicamente sintonizar. Al igual que con cualquier plataforma, no usar interrupciones ayuda en gran medida a la naturaleza determinista de la ejecución de instrucciones.
No, no es tan simple como un PIC, pero aún así es bastante factible, especialmente si el retraso/tiempo 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 la presupuesto de energía) y luego use un temporizador y tenga muchos relojes para hacer otras cosas también.
Super gato
Super gato
Super gato