Comptage de cycles avec des processeurs modernes (par exemple ARM)

14

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.

supercat
la source

Réponses:

7

Je vote pour DMA. Il est vraiment flexible dans Cortex-M3 et plus - et vous pouvez faire toutes sortes de choses folles comme obtenir automatiquement des données d'un endroit et les envoyer dans un autre avec un taux spécifié ou lors de certains événements sans dépenser de cycles de processeur. Le DMA est beaucoup plus fiable.

Mais il pourrait être assez difficile de comprendre les détails.

Une autre option est le soft-core sur FPGA avec l'implémentation matérielle de ces choses étroites.

BarsMonster
la source
1
J'aime la notion de DMA. Je ne pense pas que le noyau Cortex M3 ait un DMA, cependant - c'est une fonction des puces des fabricants individuels, et ils semblent tous l'implémenter différemment. Une chose que je trouve gênant avec au moins l'implémentation avec laquelle j'ai réellement joué (STM32L152), c'est que je ne trouve aucun moyen d'avoir un flash stroboscopique lorsque des données DMA sont sorties. Il n'est pas clair non plus quels facteurs peuvent affecter l'actualité du DMA.
supercat
1
En tout cas, en ce qui concerne l'une des premières applications que je réfléchissais pour un cycle-banging précis, j'ai posté plus d'informations dans la question d'origine. Je suis curieux de savoir ce que vous en pensez. Une autre situation où je réfléchissais au cycle-banging serait de dynamiser les données d'affichage sur un écran LCD couleur. Les données seraient mises en mémoire tampon dans la RAM en utilisant des couleurs 8 bits, mais l'affichage a besoin de couleurs 16 bits. Le moyen le plus rapide auquel j'avais pensé pour produire des données aurait été d'utiliser du matériel pour générer les stroboscopes d'écriture, de sorte que le processeur n'aurait qu'à synchroniser les données. Serait-il bon de traduire 8-> 16 bits dans un petit tampon ...
supercat
1
... et ensuite organiser DMA pour transférer cela, ou quelle serait la meilleure approche?
supercat
4

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:

extrait du 18.2

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:

[1] Les branches prennent un cycle pour l'instruction, puis le rechargement du pipeline pour l'instruction cible. Les branches non prises représentent 1 cycle au total. Les branches prises avec un immédiat sont normalement 1 cycle de rechargement de pipeline (2 cycles au total). Les branches prises avec l'opérande de registre sont normalement 2 cycles de rechargement de pipeline (3 cycles au total). Le rechargement du pipeline est plus long [combien de temps?] Lors du branchement à des instructions 32 bits non alignées en plus des accès à une mémoire plus lente. Un indice de branchement est émis vers le bus de code qui permet à un système plus lent [combien plus lent?] De précharger. Cela peut [Est-ce facultatif?] Réduire [de combien?] La pénalité de cible de branchement pour une mémoire plus lente, mais jamais moins que celle illustrée ici.

[2] Généralement, les instructions de stockage de charge prennent deux cycles pour le premier accès et un cycle pour chaque accès supplémentaire. Les magasins avec compensation immédiate prennent un cycle.

[3] UMULL / SMULL / UMLAL / SMLAL utilise une terminaison anticipée en fonction de la taille des valeurs source [Quelles tailles?]. Celles-ci sont interruptibles (abandonnées / redémarrées), avec la pire latence d'un cycle. Les versions MLAL prennent quatre à sept cycles et les versions MULL prennent trois à cinq cycles . Pour MLAL, la version signée est un cycle plus longue que la version non signée.

[4] Les instructions informatiques peuvent être pliées . [Quand? Voir les commentaires.]

[5] Les horaires DIV dépendent du dividende et du diviseur . [Même problème que MUL] DIV est interruptible (abandonné / redémarré), avec la pire latence d'un cycle. Lorsque le dividende et le diviseur sont similaires en taille [Dans quelle mesure?], La division se termine rapidement. Le temps minimum est pour les cas de diviseur supérieur au dividende et de diviseur de zéro. Un diviseur de zéro renvoie zéro (pas un défaut), bien qu'un piège de débogage soit disponible pour intercepter ce cas. [Quelles sont les plages qui ont été données pour MUL?]

[6] Le sommeil est un cycle pour l'instruction plus autant de cycles de sommeil que nécessaire. WFE n'utilise qu'un seul cycle lorsque l'événement est passé. WFI est normalement plus d'un cycle, sauf si une interruption se produit exactement lors de l'entrée WFI.

[7] L'ISB prend un cycle (agit comme une branche). DMB et DSB prennent un cycle sauf si des données sont en attente dans le tampon d'écriture ou LSU. Si une interruption survient pendant une barrière, elle est abandonnée / redémarrée.

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:

  1. Contactez votre fournisseur et demandez-lui quel est le calendrier des instructions pour votre cas d'utilisation.
  2. Test pour spécifier le comportement ambigu
  3. Testez à nouveau toutes les révisions du processeur et en particulier lors des changements de fournisseur.

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à.

Kevin Vermeer
la source
1
Je considérerais ce qui suit comme vague: «Le rechargement du pipeline est plus long lors de la ramification vers des instructions 32 bits non alignées en plus des accès à une mémoire plus lente» ne dit pas s'il ajoute précisément un cycle, et «Les instructions informatiques peuvent être pliées» ne 't spécifier dans quelles conditions ils seront ou ne seront pas.
supercat
1
Le timing "IT" semblerait particulièrement troublant, car c'est une instruction qui serait souvent utilisée dans une boucle comptée en cycles serrés, et je suis pratiquement certain qu'elle ne peut pas toujours être pliée. Je suppose que si l'on se branche toujours au début d'une boucle sensible au timing, force la boucle à démarrer à une limite de mot, évite toute charge conditionnelle ou stocke dans la boucle, et on ne met pas immédiatement d'instruction "IT" après le chargement ou la mise à jour du magasin, les horaires "IT" seraient cohérents, mais la spécification ne le précise pas.
supercat
1
Je suppose que le service informatique pourrait probablement (honnêtement) noter quelque chose comme: "En l'absence d'états d'attente ou de conflit de bus de code, le pliage informatique est garanti si (1) l'instruction précédente était une instruction 16 bits qui n'avait pas accès la mémoire ou le compteur de programmes; et (2) soit l'instruction suivante est une instruction 16 bits, soit l'instruction précédente n'était pas la cible d'une branche "non alignée". Le pliage informatique peut également se produire dans d'autres circonstances non spécifiées. " Une telle spécification permettrait d'écrire des programmes avec un temps d'instruction informatique prévisible en s'assurant que le code était organisé comme indiqué.
supercat
1
Wow - j'avoue que je n'avais fait que de simples décomptes dans le pire des cas, plutôt que d'avoir lutté avec les mises en garde sous la table. Ma réponse mise à jour met en évidence d'autres ambiguïtés.
Kevin Vermeer
1
Il y a beaucoup de situations où l'on est intéressé par le pire des cas, et un bon nombre où l'on est intéressé par le meilleur des cas (par exemple, si un port SPI peut sortir un octet tous les 16 cycles, générer chaque octet prendrait 14 cycles dans le meilleur des cas, et la vérification de la préparation prendrait 5 cycles, la vérification de la préparation dans chaque octet limiterait la vitesse à un octet tous les 19 cycles dans le meilleur des cas; l'écriture en aveugle avec deux NOP supplémentaires permettrait une vitesse d'un octet tous les 16 cycles dans le meilleur des cas ). Les cas où un timing précis est nécessaire ne sont pas aussi courants, mais ils peuvent survenir.
supercat
3

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

Leon Heller
la source
1
Je commence à penser que Leon a des parts dans XMOS ... ;-)
Federico Russo
1
J'aime juste leurs chips et les gens qui y travaillent. Parallax est également une belle entreprise avec de bons produits.
Leon Heller
1
Ouais, pas d'offense. Cela me frappe juste que toutes les réponses (sauf une) où XMOS est mentionné viennent de vous. Il n'y a rien de mal à être enthousiaste à propos de quelque chose.
Federico Russo
@Federico, @Leon - C'est exactement ce qui m'inquiète un peu à propos de XMOS: pourquoi n'y a-t-il qu'un seul utilisateur dans le monde (du moins c'est ce à quoi il ressemble)? Si c'est si génial, pourquoi ne parle-t-on pas de la ville? Je n'ai jamais entendu personne en parler, moins l'utiliser.
stevenvh
Essayez les forums XMOS: xcore.com
Leon Heller
2

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.

Olin Lathrop
la source
Je conviens qu'en général, les applications qui sont plus gourmandes en calcul deviennent moins sensibles à la synchronisation microscopique, mais il existe certains scénarios où l'on pourrait avoir besoin d'un peu plus de puissance de traitement que le PIC-18 mais aussi de prévisibilité. Je me demande dans quelle mesure je devrais m'efforcer d'apprendre des choses comme les architectures PIC 16 bits, ou dans quelle mesure je devrais penser que l'ARM sera probablement adéquat.
supercat
0

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.

old_timer
la source