Pourquoi avons-nous besoin de l'instruction "nop" Ie No operation dans le microprocesseur 8085?

19

Dans l'instruction du microprocesseur 8085, il y a une opération de commande de machine "nop" (aucune opération). Ma question est pourquoi avons-nous besoin d'une opération sans? Je veux dire que si nous devons mettre fin au programme, nous utiliserons HLT ou RST 3. Ou si nous voulons passer à l'instruction suivante, nous donnerons les instructions suivantes. Mais pourquoi pas d'opération? Quel est le besoin?

Demietra95
la source
5
NOP est fréquemment utilisé pour déboguer et mettre à jour votre programme. Si à une date ultérieure, vous souhaitez ajouter des lignes à votre programme, vous pouvez simplement remplacer le NOP. Sinon, vous devrez insérer des lignes, et insérer signifie déplacer tout le programme. Les instructions erronées (incorrectes) peuvent également être remplacées (simplement écrasées) par NOP en suivant les mêmes arguments.
Contrebande de plutonium
Ohkay. Mais l'utilisation de nop augmente également l'espace. Alors que notre objectif premier est de lui faire prendre peu de place.
Demietra95
* Je veux dire que notre objectif est de réduire le problème. Cela ne devient-il pas également un problème?
Demietra95
2
C'est pourquoi il doit être utilisé à bon escient. Sinon, votre programme entier ne sera qu'un groupe de NOP.
Contrebande de plutonium

Réponses:

34

Une utilisation de l'instruction NOP (ou NOOP, sans opération) dans les CPU et les MCU est d'insérer un petit retard prévisible dans votre code. Bien que les NOP n'effectuent aucune opération, cela prend un certain temps pour les traiter (le CPU doit récupérer et décoder l'opcode, il a donc besoin de peu de temps pour le faire). Aussi peu qu'un cycle de CPU est "gaspillé" pour exécuter une instruction NOP (le nombre exact peut être déduit de la fiche technique CPU / MCU, généralement), donc mettre N NOP en séquence est un moyen facile d'insérer un retard prévisible:

tdelay=NTclockK

où K est le nombre de cycles (le plus souvent 1) nécessaires au traitement d'une instruction NOP, et est la période d'horloge.Tclock

Pourquoi ferais-tu ça? Il peut être utile de forcer le CPU à attendre un peu que les périphériques externes (peut-être plus lents) terminent leur travail et communiquent les données au CPU, c'est-à-dire que NOP est utile à des fins de synchronisation.

Voir également la page Wikipédia correspondante sur NOP .

Une autre utilisation est d'aligner le code à certaines adresses en mémoire et d'autres "astuces d'assemblage", comme expliqué également dans ce fil sur Programmers.SE et dans cet autre fil sur StackOverflow .

Un autre article intéressant sur le sujet .

Ce lien vers une page de livre Google se réfère en particulier au processeur 8085. Extrait:

Chaque instruction NOP utilise quatre horloges pour l'extraction, le décodage et l'exécution.

EDIT (pour répondre à une préoccupation exprimée dans un commentaire)

Si vous vous inquiétez de la vitesse, gardez à l'esprit que l'efficacité (temporelle) n'est qu'un paramètre à considérer. Tout dépend de l'application: si vous voulez calculer le 10 milliardième chiffre de , alors votre seule préoccupation pourrait être la vitesse. D'un autre côté, si vous souhaitez enregistrer les données des capteurs de température connectés à un MCU via un ADC, la vitesse n'est généralement pas si importante, mais il est essentiel d' attendre le bon laps de temps pour permettre à l'ADC de terminer correctement chaque lecture . Dans ce cas, si le MCU n'attend pas suffisamment, il risque d'obtenir des données complètement non fiables (je concède qu'il obtiendrait ces données plus rapidement , cependant: o).π

Lorenzo Donati soutient Monica
la source
3
Beaucoup de choses (en particulier les sorties pilotant des circuits externes à l'UC) sont soumises à des contraintes de synchronisation comme `` le temps minimum entre D étant stable et le bord de l'horloge est de 100 us '' ou `` la LED IR doit clignoter à 1 MHz ''. Par conséquent, des retards (précis) sont souvent nécessaires.
Wouter van Ooijen
6
Les NOP peuvent être utiles pour obtenir le bon timing lors du bang-bang d'un protocole série. Ils peuvent également être utiles pour remplir l'espace de code inutilisé suivi d'un saut vers le vecteur de démarrage à froid pour les situations rares si le compteur de programmes est corrompu (par exemple, problème d'alimentation, impact par un événement rare de rayons gamma, etc.) et commence à exécuter du code dans un sinon vide une partie de l'espace de code.
Techydude
6
Sur le système informatique vidéo Atari 2600 (la deuxième console de jeu vidéo pour exécuter des programmes stockés sur des cartouches), le processeur exécuterait exactement 76 cycles par ligne de balayage, et de nombreuses opérations devraient être effectuées un certain nombre exact de cycles après le début d'une ligne de balayage. Sur ce processeur, l'instruction "NOP" documentée prend deux cycles, mais il est également courant que le code utilise une instruction à trois cycles autrement inutile pour compenser un retard à un nombre précis de cycles. L'exécution plus rapide du code aurait donné un affichage totalement tronqué.
supercat
1
L'utilisation de NOP pour les retards peut être utile même sur des systèmes non en temps réel dans les cas où un périphérique d'E / S impose un temps minimum entre les opérations consécutives, mais pas un maximum. Par exemple, sur de nombreux contrôleurs, le décalage d'un octet vers un port SPI prendra huit cycles CPU. Le code qui ne fait que récupérer des octets de la mémoire et les exporter vers le port SPI pourrait s'exécuter un peu trop rapidement, mais l'ajout de logique pour tester si le port SPI était prêt pour chaque octet le rendrait inutilement lent. L'ajout d'un NOP ou deux peut permettre au code d'atteindre la vitesse maximale disponible ...
supercat
1
... dans le cas où il n'y a pas d'interruption. Si une interruption se produit, les NOP perdraient inutilement du temps, mais le temps perdu par un ou deux NOP serait inférieur au temps nécessaire pour déterminer si une interruption les rendait inutiles.
supercat
8

Les autres réponses ne considèrent qu'un NOP qui s'exécute à un moment donné - qui est utilisé assez couramment, mais ce n'est pas la seule utilisation de NOP.

La non-exécution NOP est également très utile pour écrire du code qui peut être patché - en gros, vous aurez pad la fonction avec quelques NOP après la RET(ou une instruction similaire). Lorsque vous devez patcher l'exécutable, vous pouvez facilement ajouter plus de code à la fonction en commençant par l'original RETet en utilisant autant de ces NOP que vous en avez besoin (par exemple pour les sauts longs ou même le code en ligne) et en terminant par un autre RET.

Dans ce cas d'utilisation, noöne s'attend à ce que le soit NOPexécuté. Le seul point est de permettre de patcher l'exécutable - dans un exécutable théorique non rembourré, vous devrez en fait changer le code de la fonction elle-même (parfois cela pourrait correspondre aux limites d'origine, mais assez souvent vous aurez quand même besoin d'un saut ) - c'est beaucoup plus compliqué, surtout si l'on considère un assemblage écrit manuellement ou un compilateur d'optimisation; vous devez respecter les sauts et les constructions similaires qui auraient pu pointer sur un morceau de code important. Dans l'ensemble, assez délicat.

Bien sûr, c'était beaucoup plus utilisé dans les temps anciens, quand il était utile de faire des patchs comme ces petits et ligne . Aujourd'hui, vous allez simplement distribuer un binaire recompilé et en finir avec lui. Il y en a encore qui utilisent les correctifs NOP (exécutés ou non, et pas toujours littéraux NOP- par exemple, Windows utilise MOV EDI, EDIpour les correctifs en ligne - c'est le genre où vous pouvez mettre à jour une bibliothèque système pendant que le système est en cours d'exécution, sans avoir besoin de redémarrages).

Alors la dernière question est, pourquoi avoir une instruction dédiée pour quelque chose qui ne fait vraiment rien?

  • Il s'agit d'une instruction réelle - importante lors du débogage ou de l'assemblage de codage manuel. Des instructions commeMOV AX, AX feront exactement la même chose, mais ne signalent pas l'intention aussi clairement.
  • Remplissage - "code" qui est là juste pour améliorer les performances globales du code qui dépend de l'alignement. Il n'est jamais destiné à être exécuté. Certains débogueurs masquent simplement les NOP de remplissage dans leur démontage.
  • Il donne plus d'espace pour optimiser les compilateurs - le modèle toujours utilisé est que vous avez deux étapes de compilation, la première étant plutôt simple et produisant beaucoup de code d'assemblage inutile, tandis que la seconde nettoie, recâble les références d'adresse et supprime instructions étrangères. Cela se voit souvent dans les langages compilés par JIT également - l'IL de .NET et le code d'octet de la JVM utilisent NOPbeaucoup; le code d'assemblage compilé réel n'en a plus. Il convient de noter que ce ne sont pas des x86 NOP, cependant.
  • Cela facilite le débogage en ligne à la fois pour la lecture (la mémoire pré-mise à zéro sera tout NOP, ce qui rend le démontage beaucoup plus facile à lire) et pour le patch à chaud (bien que je préfère de loin Modifier et Continuer dans Visual Studio: P).

Pour exécuter les NOP, il y a bien sûr quelques points supplémentaires:

  • Performances, bien sûr - ce n'est pas pourquoi c'était en 8085, mais même le 80486 avait déjà une exécution d'instructions en pipeline, ce qui rend "ne rien faire" un peu plus délicat.
  • Comme vu avec MOV EDI, EDI, il y a d'autres NOP efficaces que le littéral NOP. MOV EDI, EDIa les meilleures performances en tant que NOP à 2 octets sur x86. Si vous avez utilisé deux NOPs, ce serait deux instructions à exécuter.

ÉDITER:

En fait, la discussion avec @DmitryGrigoryev m'a obligé à y réfléchir un peu plus, et je pense que c'est un ajout précieux à cette question / réponse, alors laissez-moi ajouter quelques bits supplémentaires:

Premièrement, je veux dire, évidemment - pourquoi y aurait-il une instruction qui ferait quelque chose comme ça mov ax, ax? Par exemple, regardons le cas du code machine 8086 (plus ancien que même le code machine 386):

  • Il y a une instruction NOP dédiée avec opcode 0x90. C'est encore le moment où beaucoup de gens ont écrit en assemblée, faites attention. Donc, même s'il n'y avait pas d' NOPinstruction dédiée , le NOPmot - clé (alias / mnémonique) serait toujours utile et correspondrait à cela.
  • Des instructions telles MOVque mapper réellement vers de nombreux opcodes différents, car cela économise du temps et de l'espace - par exemple, mov al, 42"déplacer l'octet immédiat dans le alregistre", ce qui se traduit par 0xB02A( 0xB0étant l'opcode,0x2A étant l'argument "immédiat"). Cela prend donc deux octets.
  • Il n'y a pas d'opcode de raccourci pour mov al, al(puisque c'est une chose stupide à faire, fondamentalement), vous devrez donc utiliser la mov al, rmbsurcharge (rmb étant "registre ou mémoire"). Cela prend en fait trois octets. (bien qu'il utiliserait probablement le moins spécifique à la mov rb, rmbplace, qui ne devrait prendre que deux octets mov al, al- l'argument d'argument est utilisé pour spécifier à la fois le registre source et le registre cible; maintenant vous savez pourquoi 8086 n'avait que 8 registres: D). Comparez à NOP, qui est une instruction à un octet! Cela économise de la mémoire et du temps, car la lecture de la mémoire dans le 8086 était encore assez coûteuse - sans parler du chargement de ce programme à partir d'une bande ou d'une disquette ou quelque chose, bien sûr.

Alors d'où xchg ax, axvient-il? Il suffit de regarder les opcodes des autres xhcginstructions. Vous verrez 0x86, 0x87et enfin, 0x91- 0x97. Donc, nopavec, cela 0x90semble être un assez bon ajustement xchg ax, ax(qui, encore une fois, n'est pas une xchg"surcharge" - vous devrez utiliser xchg rb, rmb, à deux octets). Et en fait, je suis presque sûr que c'était un bel effet secondaire de la micro-architecture de l'époque - si je me souviens bien, il était facile de mapper toute la gamme de 0x90-0x97"xchg, agissant sur des registres axet ax- di" ( l'opérande étant symétrique, cela vous a donné la gamme complète, y compris le nop xchg ax, ax; notez que l'ordre est ax, cx, dx, bx, sp, bp, si, di- bxest après dx,ax; rappelez-vous, les noms de registre sont des mnémoniques, pas des noms ordonnés - accumulateur, compteur, données, base, pointeur de pile, pointeur de base, index source, index destination). La même approche a également été utilisée pour d'autres opérandes, par exemple l' mov someRegister, immediateensemble. D'une certaine manière, vous pourriez penser à cela comme si l'opcode n'était pas un octet entier - les derniers bits sont "un argument" pour l'opérande "réel".

Tout cela dit, sur x86, noppeut être considéré comme une vraie instruction ou non. La micro-architecture originale le traitait comme une variante de xchgsi je me souviens bien, mais elle était en fait nommée nopdans la spécification. Et comme cela xchg ax, axn'a pas vraiment de sens en tant qu'instruction, vous pouvez voir comment les concepteurs du 8086 ont économisé sur les transistors et les voies dans le décodage des instructions en exploitant le fait que cela 0x90correspond naturellement à quelque chose qui est entièrement "nul".

D'un autre côté, le i8051 possède un opcode entièrement conçu pour nop- 0x00. Un peu pratique. La conception d'instruction utilise essentiellement le demi - octet pour le fonctionnement et le bit de poids faible pour la sélection des opérandes - par exemple, add aest 0x2Y, et 0xX8signifie « registre 0 direct », 0x28c'est add a, r0. Économise beaucoup sur le silicium :)

Je pourrais continuer, car la conception du processeur (sans parler de la conception du compilateur et de la conception du langage) est un sujet assez large, mais je pense que j'ai montré de nombreux points de vue différents qui sont entrés assez bien dans la conception.

Luaan
la source
En fait, NOPest généralement un alias MOV ax, ax, ADD ax, 0ou des instructions similaires. Pourquoi voudriez-vous concevoir une instruction dédiée qui ne fait rien alors qu'il y en a beaucoup là-bas.
Dmitry Grigoryev
@DmitryGrigoryev Cela entre vraiment dans la conception du langage CPU (enfin, la micro-architecture) lui-même. La plupart des processeurs (et compilateurs) auront tendance à optimiser le MOV ax, axdéplacement; NOPaura toujours un nombre fixe de cycles pour l'exécution. Mais je ne vois pas comment cela est pertinent à ce que j'ai écrit dans ma réponse de toute façon.
Luaan
Les processeurs ne peuvent pas vraiment optimiser une MOV ax, axdistance, car au moment où ils savent que c'est une MOVinstruction qui est déjà dans le pipeline.
Dmitry Grigoryev
@DmitryGrigoryev Cela dépend vraiment du type de CPU dont vous parlez. Les processeurs de bureau modernes font beaucoup de choses, pas seulement des pipelines d'instructions. Par exemple, le CPU sait qu'il n'a pas à invalider les lignes de cache, etc., il sait qu'il n'a rien à faire (très important pour HyperThreading, et même pour les multiples canaux impliqués en général). Je ne serais pas surpris si cela influençait également la prédiction de branche (bien que ce serait probablement la même chose pour NOPet MOV ax, ax). Les processeurs modernes sont bien plus complexes que les compilateurs C oldschool :))
Luaan
1
+1 pour l'optimisation de personne à noöne! Je pense que nous devrions tous coopérer et revenir à ce style d'orthographe! Le Z80 (et à son tour le 8080) a 7 LD r, rinstructions où se rtrouve un seul registre, similaire à votre MOV ax, axinstruction. La raison pour laquelle c'est 7 et non 8 est parce qu'une des instructions est surchargée pour devenir HALT. Les 8080 et Z80 ont donc au moins 7 autres instructions qui font la même chose que NOP! Chose intéressante, même si ces instructions ne sont pas logiquement liées par motif binaire, elles prennent toutes 4 états T à exécuter, il n'y a donc aucune raison d'utiliser les LDinstructions!
CJ Dennis
5

À la fin des années 70, nous (j'étais alors un jeune étudiant en recherche) avions un petit système de développement (8080 si la mémoire est utilisée) qui fonctionnait en 1024 octets de code (c'est-à-dire une seule UVEPROM) - il n'avait que quatre commandes à charger (L ), enregistrer (S), imprimer (P) et autre chose dont je ne me souviens pas. Il était piloté avec un vrai téléscripteur et une bande perforée. C'était bien codé!

Un exemple d'utilisation de NOOP était dans une routine de service d'interruption (ISR), qui étaient espacées à des intervalles de 8 octets. Cette routine a fini par être longue de 9 octets, se terminant par un (long) saut vers une adresse légèrement plus haut dans l'espace adresse. Cela signifiait, étant donné le petit ordre d'octets endian, que l'octet d'adresse haute était 00h, et inséré dans le premier octet du prochain ISR, ce qui signifiait qu'il (le prochain ISR) commençait par NOOP, juste pour que `` nous '' puisse tenir le code dans l'espace limité!

Le NOOP est donc utile. De plus, je soupçonne qu'il était plus facile pour Intel de le coder de cette façon - Ils avaient probablement une liste d'instructions qu'ils voulaient implémenter et cela a commencé à '1', comme toutes les listes (c'était l'époque de FORTRAN), donc le zéro Le code NOOP est devenu une retombée. (Je n'ai jamais vu un article affirmant qu'un NOOP est un élément essentiel de la théorie des sciences informatiques (le même Q que: les mathématiciens ont-ils un nul op, différent du zéro de la théorie des groupes?)

Philip Oakley
la source
Tous les processeurs n'ont pas codé NOP à 0x00 (bien que le 8085, le 8080 et le processeur que je connais le mieux, le Z80, le fassent tous). Cependant, si je concevais un processeur, c'est là que je le mettrais! Quelque chose d'autre qui est pratique est que la mémoire est généralement initialisée à tous les 0x00, donc l'exécuter en tant que code ne fera rien jusqu'à ce que le CPU atteigne la mémoire non mise à zéro.
CJ Dennis
@CJDennis J'ai expliqué pourquoi les CPU x86 n'utilisent pas 0x00pour nopdans ma réponse. En bref, il économise sur le décodage des instructions - xchg ax, axdécoule naturellement de la façon dont le décodage des instructions fonctionne, et il fait quelque chose de "nul", alors pourquoi ne pas utiliser cela et l'appeler nop, non? :) Utilisé pour économiser un peu sur le silicium pour le décodage des instructions ...
Luaan
5

Sur certaines architectures, NOPest utilisé pour occuper les créneaux de retard inutilisés . Par exemple, si l'instruction de branchement n'efface pas le pipeline, plusieurs instructions après son exécution sont quand même exécutées:

 JMP     .label
 MOV     R2, 1    ; these instructions start execution before the jump
 MOV     R2, 2    ; takes place so they still get executed

Mais que se passe-t-il si vous ne disposez pas d'instructions utiles à adapter après JMP? Dans ce cas, vous devrez utiliser l' NOPart.

Les créneaux de retard ne sont pas limités aux sauts. Sur certaines architectures, les risques de données dans le pipeline CPU ne sont pas résolus automatiquement. Cela signifie qu'après chaque instruction qui modifie un registre, il y a un slot où la nouvelle valeur du registre n'est pas encore accessible. Si l'instruction suivante a besoin de cette valeur, l'emplacement doit être occupé par NOP:

 ADD     R1, R1
 NOP               ; The new value of R1 is not ready yet
 ADD     R1, R3

De plus, certaines instructions d'exécution conditionnelle ( If-True-False et similaires) utilisent des emplacements pour chaque condition, et lorsqu'une condition particulière n'a aucune action associée, son emplacement doit être occupé par NOP:

CMP     R0, R1       ; Compare R0 and R1, setting flags
ITF     GT           ; If-True-False on GT flag 
MOV     R3, R2       ; If 'greater than', move R2 to R3
NOP                  ; Else, do nothing
Dmitry Grigoryev
la source
+1. Bien sûr, ceux-ci ont seulement tendance à apparaître sur des architectures qui ne se soucient pas de la compatibilité descendante - si x86 essayait quelque chose comme ça lors de l'introduction du pipelining d'instructions, presque tout le monde l'appellerait simplement mal (après tout, ils ont juste mis à niveau leur CPU et leur les applications ont cessé de fonctionner!). Donc x86 doit s'assurer que l'application ne remarque pas quand des améliorations comme celle-ci sont ajoutées au CPU - jusqu'à ce que nous
arrivions aux
2

Un autre exemple d'utilisation d'un NOP à deux octets : http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

L'instruction MOV EDI, EDI est un NOP à deux octets, ce qui est juste assez d'espace pour patcher dans une instruction de saut afin que la fonction puisse être mise à jour à la volée. L'intention est que l'instruction MOV EDI, EDI soit remplacée par une instruction JMP $ -5 de deux octets pour rediriger le contrôle vers cinq octets d'espace de correctif immédiatement avant le début de la fonction. Cinq octets suffisent pour une instruction de saut complet, qui peut envoyer le contrôle à la fonction de remplacement installée ailleurs dans l'espace d'adressage.

pjc50
la source