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.