Dans de nombreuses applications, un processeur dont l'exécution des instructions a une relation temporelle connue avec les stimuli d'entrée attendus peut gérer des tâches qui nécessiteraient un processeur beaucoup plus rapide si la relation était inconnue. Par exemple, dans un projet que j'ai fait en utilisant un PSOC pour générer de la vidéo, j'ai utilisé du code pour sortir un octet de données vidéo toutes les 16 horloges CPU. Étant donné que tester si le périphérique SPI est prêt et se ramifier sinon, l'IIRC prendrait 13 horloges et qu'une charge et un stockage pour produire les données en prendraient 11, il n'y avait aucun moyen de tester la disponibilité du périphérique entre les octets; au lieu de cela, j'ai simplement arrangé que le processeur exécute précisément 16 cycles de code pour chaque octet après le premier (je crois que j'ai utilisé une charge indexée réelle, une charge indexée factice et un magasin). La première écriture SPI de chaque ligne a eu lieu avant le début de la vidéo, et pour chaque écriture suivante, il y avait une fenêtre de 16 cycles où l'écriture pouvait se produire sans dépassement ni sous-exécution de la mémoire tampon. La boucle de branchement a généré une fenêtre d'incertitude de 13 cycles, mais l'exécution prévisible de 16 cycles signifiait que l'incertitude pour tous les octets ultérieurs correspondrait à cette même fenêtre de 13 cycles (qui à son tour s'inscrit dans la fenêtre de 16 cycles du moment où l'écriture pourrait être acceptable). se produire).
Pour les CPU plus anciens, les informations de synchronisation des instructions étaient claires, disponibles et sans ambiguïté. Pour les ARM plus récents, les informations de synchronisation semblent beaucoup plus vagues. Je comprends que lorsque le code s'exécute à partir de Flash, le comportement de mise en cache peut rendre les choses beaucoup plus difficiles à prévoir, donc je m'attends à ce que tout code compté par cycle soit exécuté à partir de la RAM. Même lors de l'exécution de code à partir de la RAM, les spécifications semblent un peu vagues. L'utilisation de code compté par cycle est-elle toujours une bonne idée? Si oui, quelles sont les meilleures techniques pour le faire fonctionner de manière fiable? Dans quelle mesure peut-on supposer en toute sécurité qu'un fournisseur de puces ne glissera pas silencieusement une puce "nouvelle améliorée" qui réduit le cycle d'exécution de certaines instructions dans certains cas?
En supposant que la boucle suivante commence sur une limite de mot, comment déterminerait-on précisément en fonction des spécifications combien de temps cela prendrait (supposons que Cortex-M3 avec une mémoire à état d'attente zéro; rien d'autre sur le système ne devrait avoir d'importance pour cet exemple).
myloop: mov r0, r0; Instructions simples et brèves pour permettre la lecture préalable de plus d'instructions mov r0, r0; Instructions simples et brèves pour permettre la lecture préalable de plus d'instructions mov r0, r0; Instructions simples et brèves pour permettre la lecture préalable de plus d'instructions mov r0, r0; Instructions simples et brèves pour permettre la lecture préalable de plus d'instructions mov r0, r0; Instructions simples et brèves pour permettre la lecture préalable de plus d'instructions mov r0, r0; Instructions simples et brèves pour permettre la lecture préalable de plus d'instructions ajoute r2, r1, # 0x12000000; Instruction en 2 mots ; Répétez ce qui suit, éventuellement avec différents opérandes ; Continuera d'ajouter des valeurs jusqu'à ce qu'un report se produise itcc addcc r2, r2, # 0x12000000; Instruction de 2 mots, plus "mot" supplémentaire pour itcc itcc addcc r2, r2, # 0x12000000; Instruction de 2 mots, plus "mot" supplémentaire pour itcc itcc addcc r2, r2, # 0x12000000; Instruction de 2 mots, plus "mot" supplémentaire pour itcc itcc addcc r2, r2, # 0x12000000; Instruction de 2 mots, plus "mot" supplémentaire pour itcc ; ... etc, avec des instructions plus conditionnelles en deux mots sous r8, r8, # 1 bpl myloop
Pendant l'exécution des six premières instructions, le noyau aurait le temps de récupérer six mots, dont trois seraient exécutés, de sorte qu'il pourrait y avoir jusqu'à trois prélecture. Les instructions suivantes sont toutes les trois mots chacune, il ne serait donc pas possible pour le noyau de récupérer des instructions aussi rapidement qu'elles sont en cours d'exécution. Je m'attendrais à ce que certaines des instructions "it" prennent un cycle, mais je ne sais pas prédire lesquelles.
Ce serait bien si ARM pouvait spécifier certaines conditions dans lesquelles le timing de l'instruction "it" serait déterministe (par exemple, s'il n'y a pas d'état d'attente ou de conflit de bus de code, et que les deux instructions précédentes sont des instructions de registre 16 bits, etc.) mais je n'ai pas vu une telle spécification.
Exemple d'application
Supposons que l'on essaie de concevoir une carte fille pour un Atari 2600 pour générer une sortie vidéo composante à 480P. Le 2600 a une horloge de pixels de 3 579 MHz et une horloge de processeur de 1,19 MHz (horloge à points / 3). Pour la vidéo composante 480P, chaque ligne doit être émise deux fois, ce qui implique une sortie d'horloge à points à 7,158 MHz. Étant donné que la puce vidéo (TIA) d'Atari émet l'une des 128 couleurs en utilisant un signal luma 3 bits plus un signal de phase avec une résolution d'environ 18 ns, il serait difficile de déterminer avec précision la couleur simplement en regardant les sorties. Une meilleure approche serait d'intercepter les écritures dans les registres de couleurs, d'observer les valeurs écrites et de fournir à chaque registre les valeurs de luminance TIA correspondant au numéro de registre.
Tout cela pourrait être fait avec un FPGA, mais certains appareils ARM assez rapides peuvent être beaucoup moins chers qu'un FPGA avec suffisamment de RAM pour gérer la mise en mémoire tampon nécessaire (oui, je sais que pour les volumes une telle chose pourrait être produite, le coût n'est pas '' t un vrai facteur). Cependant, obliger l'ARM à surveiller le signal d'horloge entrant augmenterait considérablement la vitesse du processeur requise. Le nombre de cycles prévisibles pourrait rendre les choses plus propres.
Une approche de conception relativement simple consisterait à demander à un CPLD de surveiller le CPU et le TIA et de générer un signal de synchronisation RVB + 13 bits, puis de demander à ARM DMA de saisir des valeurs 16 bits d'un port et de les écrire sur un autre avec un timing approprié. Ce serait un défi de conception intéressant, cependant, de voir si un ARM bon marché pouvait tout faire. Le DMA pourrait être un aspect utile d'une approche tout-en-un si ses effets sur le nombre de cycles du processeur pouvaient être prédits (en particulier si les cycles DMA pouvaient se produire dans des cycles lorsque le bus mémoire était autrement inactif), mais à un moment donné du processus l'ARM devrait exécuter ses fonctions de recherche de table et d'observation de bus. Notez que contrairement à de nombreuses architectures vidéo où les registres de couleurs sont écrits pendant les intervalles de suppression, l'Atari 2600 écrit fréquemment dans les registres de couleurs pendant la partie affichée d'une image,
La meilleure approche serait peut-être d'utiliser quelques puces à logique discrète pour identifier les écritures de couleur et forcer les bits inférieurs des registres de couleur aux valeurs appropriées, puis utiliser deux canaux DMA pour échantillonner le bus CPU entrant et les données de sortie TIA, et un troisième canal DMA pour générer les données de sortie. Le processeur serait alors libre de traiter toutes les données des deux sources pour chaque ligne de balayage, d'effectuer la traduction nécessaire et de les mettre en mémoire tampon pour la sortie. Le seul aspect des tâches de l'adaptateur qui devrait se produire en "temps réel" serait le remplacement des données écrites sur COLUxx, et qui pourrait être pris en charge en utilisant deux puces logiques communes.
la source
Des informations sur le calendrier sont disponibles, mais, comme vous l'avez souligné, elles peuvent parfois être vagues. Il y a beaucoup d'informations de synchronisation dans la section 18.2 et le tableau 18.1 du manuel de référence technique pour le Cortex-M3, par exemple ( pdf ici ), et un extrait ici:
qui donnent une liste de conditions pour un timing maximum. Le calendrier de nombreuses instructions dépend de facteurs externes, dont certains laissent des ambiguïtés. J'ai mis en évidence chacune des ambiguïtés que j'ai trouvées dans l'extrait suivant de cette section:
Pour tous les cas d'utilisation, il sera plus complexe que le calcul "Cette instruction est un cycle, cette instruction est deux cycles, c'est un cycle ..." possible dans des processeurs plus simples, plus lents et plus anciens. Pour certains cas d'utilisation, vous ne rencontrerez aucune ambiguïté. Si vous rencontrez des ambiguïtés, je suggère:
Ces exigences répondent probablement à votre question: «Non, ce n'est pas une bonne idée, à moins que les difficultés rencontrées en valent le coût» - mais vous le saviez déjà.
la source
Une façon de contourner ce problème consiste à utiliser des appareils avec des temporisations déterministes ou prévisibles, tels que l'hélice Parallax et les puces XMOS:
http://www.parallaxsemiconductor.com/multicoreconcept
http://www.xmos.com/
Le comptage de cycle fonctionne très bien avec l'hélice (un langage d'assemblage doit être utilisé), tandis que les appareils XMOS ont un utilitaire logiciel très puissant, le XMOS Timing Analyzer, qui fonctionne avec des applications écrites dans le langage de programmation XC:
https://www.xmos.com/download/public/XMOS-Timing-Analyzer-Whitepaper%281%29.pdf
la source
Le comptage de cycles devient plus problématique à mesure que vous vous éloignez des microcontrôleurs de bas niveau et que vous passez à des processeurs informatiques plus généraux. Les premiers ont généralement un calendrier d'instructions bien spécifié, en partie pour les raisons de votre site. C'est aussi parce que leur architecture est assez simple, donc les temps d'instruction sont fixes et connaissables.
La plupart des PIC Microchip en sont un bon exemple. Les séries 10, 12, 16 et 18 ont un timing d'instruction très bien documenté et prévisible. Cela peut être une fonctionnalité utile dans le type de petites applications de contrôle auxquelles ces puces sont destinées.
Comme vous vous éloignez de l'ultra low cost, et que le concepteur peut donc dépenser plus de puce pour obtenir une vitesse plus élevée d'une architecture plus exotique, vous vous éloignez également de la prévisibilité. Jetez un œil aux variantes x86 modernes comme exemples extrêmes de cela. Il existe plusieurs niveaux de caches, de vitualisation de la mémoire, de recherche d'anticipation, de pipelining, etc., ce qui rend le comptage des cycles d'instructions presque impossible. Dans cette application, cela n'a pas d'importance, car le client est intéressé par la prévisibilité de la vitesse élevée et non du temps d'instruction.
Vous pouvez même voir cet effet à l'œuvre dans les modèles Microchip supérieurs. Le cœur 24 bits (séries 24, 30 et 33) a une synchronisation d'instructions largement prévisible, à quelques exceptions près lorsqu'il y a des conflits de bus de registre. Par exemple, dans certains cas, la machine insère un décrochage lorsque l'instruction suivante utilise un registre avec certains modes d'adressage indirect dont la valeur a été modifiée dans l'instruction précédente. Ce type de décrochage est inhabituel sur un dsPIC, et la plupart du temps vous pouvez l'ignorer, mais il montre comment ces choses se glissent en raison des concepteurs qui essaient de vous donner un processeur plus rapide et plus performant.
La réponse de base est donc que cela fait partie du compromis lorsque vous choisissez un processeur. Pour les petites applications de contrôle, vous pouvez choisir quelque chose de petit, bon marché, de faible puissance et avec un calendrier d'instruction prévisible. Au fur et à mesure que vous exigez plus de puissance de traitement, l'architecture change de sorte que vous devez abandonner le timing prévisible des instructions. Heureusement, cela pose moins de problèmes lorsque vous accédez à des applications à plus forte intensité de calcul et à usage général, donc je pense que les compromis fonctionnent assez bien.
la source
Oui, vous pouvez toujours le faire, même sur un ARM. Le plus gros problème avec cela sur un ARM est que ARM vend des cœurs et non des puces, et le timing des cœurs est connu, mais ce que le fournisseur de puces entoure varie d'un fournisseur à l'autre et parfois d'une famille de puces à une autre au sein du fournisseur. Ainsi, une puce particulière d'un fournisseur particulier peut être assez déterministe (si vous n'utilisez pas de caches par exemple), mais devient plus difficile à porter. Lorsque vous traitez avec 5 horloges ici et 11 horloges là-bas en utilisant des minuteries est problématique car le nombre d'instructions qu'il faut pour échantillonner la minuterie et déterminer si votre délai a expiré. D'après les sons de votre expérience de programmation passée, je suis prêt à parier que vous déboguez probablement avec un oscilloscope comme je le fais, afin que vous puissiez essayer une boucle serrée sur la puce à la fréquence d'horloge, regarder le spi ou l'i2c ou n'importe quelle forme d'onde, ajouter ou supprimer les nops, changer le nombre de fois dans la boucle et essentiellement régler. Comme pour toute plate-forme, ne pas utiliser d'interruptions facilite grandement la nature déterministe de l'exécution des instructions.
Non, ce n'est pas aussi simple qu'un PIC, mais tout de même tout à fait faisable, surtout si le retard / timing approche de la fréquence d'horloge du processeur. Un certain nombre de fournisseurs basés sur ARM vous permettent de multiplier la fréquence d'horloge et d'obtenir disons 60 MHz sur une référence de 8 MHz, donc si vous avez besoin d'une interface de 2 MHz au lieu de faire quelque chose toutes les 4 instructions, vous pouvez augmenter l'horloge (si vous avez le puis utilisez une minuterie et donnez-vous beaucoup d'horloges pour faire d'autres choses également.
la source