20 votos

¿Por qué necesitamos la instrucción "nop", es decir, de no operación, en el microprocesador 8085?

En la instrucción del microprocesador 8085, hay una operación de control de la máquina "nop"(ninguna operación). Mi pregunta es ¿por qué necesitamos una operación no? Quiero decir que si tenemos que terminar el programa usaremos HLT o RST 3. O si queremos pasar a la siguiente instrucción daremos las siguientes instrucciones. ¿Pero por qué ninguna operación? ¿Cuál es la necesidad?

6 votos

NOP se utiliza con frecuencia en la depuración y actualización de su programa. Si en una fecha posterior, desea añadir algunas líneas a su programa, puede simplemente sobrescribir el NOP. De lo contrario, tendrá que insertar líneas, y la inserción significa desplazar todo el programa. También las instrucciones defectuosas (incorrectas) pueden ser reemplazadas (simplemente sobrescritas) por NOP siguiendo los mismos argumentos.

0 votos

Ah, sí. Pero usar nop también es aumentar el espacio. Mientras que nuestro objetivo principal es hacer que ocupe poco espacio.

0 votos

* Quiero decir que nuestro objetivo es hacer un problema más pequeño. Entonces, ¿no se convierte en un problema también?

35voto

Lorenzo Donati Puntos 6644

Un codificador disperso es algo así como la mitad de un autocodificador. Un autocodificador funciona como:

input  =>  neural net layer  =>  hidden outputs => neural net layer => output

Para la retropropagación, la señal de error, la pérdida, es: entrada - salida

Si aplicamos una restricción de escasez a las salidas ocultas, la mayoría serán ceros y unos pocos serán 1s. Entonces la segunda capa es esencialmente un conjunto de funciones de base lineal, que se suman, de acuerdo a cuáles de las salidas ocultas son 1s.

En la codificación dispersa, sólo tenemos la segunda mitad de esto:

                                codes => neural net layer => output

Los "códigos" son un montón de números reales que seleccionan las funciones base representadas por los pesos en la capa de la red neuronal. Como en el artículo de Olshausen se aplica una restricción de escasez a los códigos, éstos son, al igual que en el autocodificador disperso, escasos: la mayoría son ceros con unos pocos unos.

La diferencia la podemos ver ahora claramente: para la codificación dispersa, no hay una primera mitad de la red neuronal: los códigos no nos los proporciona automáticamente una red neuronal.

¿Cómo obtenemos los códigos en codificación dispersa? Tenemos que optimizarnos, lo que hacemos utilizando el descenso de gradiente o algo similar, para encontrar el conjunto de códigos que mejor proporcione una salida que coincida con la imagen de entrada. Tenemos que hacer esto para cada imagen, incluso para cada imagen de prueba, cada vez.

0 votos

Bien. Lógica aceptada. ¿Pero por qué se necesita un retraso en un programa? Quiero decir, ¿no es al revés, el tiempo de ejecución debería ser más rápido para un programa eficiente? ¿El retraso es necesario para obtener la entrada de los periféricos correctamente o algo más?

3 votos

Muchas cosas (especialmente las salidas que conducen a ICs externos al uC) están sujetas a restricciones de tiempo como 'el tiempo mínimo entre que D es estable y el borde de reloj es de 100 us', o 'el LED IR debe parpadear a 1 MHz'. Por lo tanto, a menudo se requieren retrasos (precisos).

0 votos

@Lorenzo Donati ya lo ha señalado: delay(NOP) es necesario cuando se quiere sincronizar algo. Por ejemplo, en ejecución de instrucciones en cadena (Perdón por las siglas no inglesas, pero no pude encontrar una imagen más apropiada). En resumen: aquí la instrucción3 escribiría en la memoria del operando desde donde se necesita leer la instrucción6, al mismo tiempo.

9voto

Luaan Puntos 171

Las otras respuestas sólo consideran un NOP que realmente se ejecuta en algún momento - que se utiliza con bastante frecuencia, pero no es el único uso de NOP.

El NOP que no se ejecuta también es bastante útil cuando se escribe código que puede ser parcheado - básicamente, se rellena la función con unos cuantos NOPs después de el RET (o una instrucción similar). Cuando tengas que parchear el ejecutable, puedes añadir fácilmente más código a la función partiendo del original RET y utilizando tantos NOPs como sea necesario (por ejemplo, para saltos largos o incluso código inline) y terminando con otro RET .

En este caso de uso, nunca se espera que el NOP se ejecute. El único punto es permitir Parcheando el ejecutable - en un teórico ejecutable no acolchado, tendrías que cambiar realmente el código de la función en sí (a veces puede encajar en los límites originales, pero muy a menudo necesitarás un salto de todos modos) - eso es mucho más complicado, especialmente considerando un ensamblaje escrito manualmente o un compilador optimizador; tienes que respetar los saltos y construcciones similares que podrían haber apuntado a alguna pieza importante de código. En definitiva, bastante complicado.

Por supuesto, esto se usaba mucho más en los viejos tiempos, cuando era útil hacer parches como estos pequeño y en línea . Hoy en día, simplemente se distribuye un binario recompilado y listo. Todavía hay quien utiliza Parcheando NOPs (ejecutando o no, y no siempre literal NOP s - por ejemplo, Windows utiliza MOV EDI, EDI para Parcheando en línea - es el tipo en el que se puede actualizar una biblioteca del sistema mientras el sistema está realmente funcionando, sin necesidad de reiniciar).

Así que la última pregunta es, ¿por qué tener una instrucción dedicada a algo que realmente no hace nada?

  • Es una instrucción real - importante cuando se depura o se codifica manualmente el ensamblaje. Instrucciones como MOV AX, AX harán exactamente lo mismo, pero no señalan la intención con tanta claridad.
  • Padding - "código" que está ahí sólo para mejorar el rendimiento general del código que depende de la alineación. Nunca está destinado a ejecutarse. Algunos depuradores simplemente ocultan los NOP de relleno en su desensamblaje.
  • Esto da más espacio para la optimización de los compiladores - el patrón que todavía se utiliza es que tienes dos pasos de compilación, el primero es bastante simple y produce un montón de código ensamblador innecesario, mientras que el segundo limpia, reordena las referencias de dirección y elimina las instrucciones extrañas. Esto también se ve a menudo en los lenguajes compilados en JIT - tanto el IL de .NET como el código de bytes de JVM utilizan NOP s bastante; el código ensamblador realmente compilado ya no los tiene. Hay que tener en cuenta que esos no son x86- NOP s, sin embargo.
  • Facilita la depuración en línea tanto para la lectura (la memoria pre-cero será todo- NOP s, lo que hace que el desensamblaje sea mucho más fácil de leer) y para hot-Parcheando (aunque prefiero de lejos Editar y Continuar en Visual Studio :P).

Para la ejecución de los PON, hay por supuesto algunos puntos más:

  • Rendimiento, por supuesto - esto no es por lo que estaba en el 8085, pero incluso el 80486 ya tenía una ejecución de instrucciones en tuberías, lo que hace que "no hacer nada" sea un poco más complicado.
  • Como se ve con MOV EDI, EDI hay otros NOP efectivos además del literal NOP . MOV EDI, EDI tiene el mejor rendimiento como NOP de 2 bytes en x86. Si se utilizan dos NOP s, serían dos instrucciones a ejecutar.

EDITAR:

En realidad, la discusión con @DmitryGrigoryev me obligó a pensar en esto un poco más, y creo que es una adición valiosa a esta pregunta / respuesta, así que permítanme añadir algunos bits adicionales:

Primero, el punto, obviamente - por qué habría una instrucción que hace algo como mov ax, ax ? Por ejemplo, veamos el caso del código máquina 8086 (más antiguo incluso que el código máquina 386):

  • Hay una instrucción NOP dedicada con opcode 0x90 . Todavía es la época en la que mucha gente escribía en ensamblador, eso sí. Así que incluso si no había un dedicado NOP instrucción, el NOP (alias/mnemónico) seguiría siendo útil y se asignaría a eso.
  • Instrucciones como MOV en realidad se asignan a muchos opcodes diferentes, porque eso ahorra tiempo y espacio - por ejemplo, mov al, 42 es "mover el byte inmediato al al registro", que se traduce en 0xB02A ( 0xB0 siendo el opcode, 0x2A siendo el argumento "inmediato"). Por lo tanto, se necesitan dos bytes.
  • No hay un opcode abreviado para mov al, al (ya que eso es una estupidez, básicamente), por lo que tendrá que utilizar el mov al, rmb (rmb es "registro o memoria") sobrecarga. En realidad, eso lleva tres bytes. (aunque probablemente utilizaría el menos específico mov rb, rmb en su lugar, que sólo debería tomar dos bytes para mov al, al - el byte de argumento se utiliza para especificar tanto el registro de origen como el de destino; ahora ya sabe por qué el 8086 sólo tenía 8 registros :D). Compare con NOP que es una instrucción de un solo byte. Esto ahorra memoria y tiempo, ya que la lectura de la memoria en el 8086 seguía siendo bastante cara - por no hablar de la carga de ese programa desde una cinta o disquete o algo así, por supuesto.

Entonces, ¿dónde está el xchg ax, ax ¿vienen? Sólo hay que mirar los opcodes de los otros xhcg instrucciones. Verás 0x86 , 0x87 y finalmente, 0x91 - 0x97 . Así que nop con su 0x90 parece una buena opción para xchg ax, ax (que, de nuevo, no es una xchg "sobrecarga" - tendrías que usar xchg rb, rmb a dos bytes). Y, de hecho, estoy bastante seguro de que esto era un buen efecto secundario de la microarquitectura de la época: si no recuerdo mal, era fácil asignar toda la gama de 0x90-0x97 a "xchg, actuando sobre los registros ax y ax - di " (siendo el operando simétrico, esto le dio el rango completo, incluyendo el nop xchg ax, ax ; tenga en cuenta que el orden es ax, cx, dx, bx, sp, bp, si, di - bx es después de dx no ax Recuerde que los nombres de los registros son mnemotécnicos, no nombres ordenados: acumulador, contador, datos, base, puntero de pila, puntero de base, índice de origen, índice de destino). El mismo enfoque se utilizó también para otros operandos, por ejemplo el mov someRegister, immediate conjunto. En cierto modo, se podría pensar en esto como si el opcode no fuera realmente un byte completo: los últimos bits son "un argumento" para el operando "real".

Dicho esto, en x86, nop puede considerarse una instrucción real, o no. La microarquitectura original sí la trataba como una variante de xchg si no recuerdo mal, pero en realidad se llamaba nop en el pliego de condiciones. Y como xchg ax, ax no tiene realmente sentido como instrucción, se puede ver cómo los diseñadores del 8086 ahorraron en transistores y caminos en la decodificación de instrucciones explotando el hecho de que 0x90 se traslada de forma natural a algo que es totalmente "tonto".

Por otro lado, el i8051 tiene un opcode completamente diseñado para nop - 0x00 . Algo práctico. El diseño de la instrucción es básicamente usar el nibble alto para la operación y el nibble bajo para seleccionar los operandos - por ejemplo, add a es 0x2Y y 0xX8 significa "registro 0 directo", por lo que 0x28 es add a, r0 . Ahorra mucho en silicio :)

Podría seguir, ya que el diseño de la CPU (por no hablar del diseño del compilador y el diseño del lenguaje) es un tema bastante amplio, pero creo que he mostrado muchos puntos de vista diferentes que entraron en el diseño bastante bien como está.

0 votos

En realidad, NOP suele ser un alias de MOV ax, ax , ADD ax, 0 o instrucciones similares. Por qué diseñar una instrucción dedicada que no hace nada cuando hay muchas por ahí.

0 votos

@DmitryGrigoryev Eso sí que entra en el diseño del propio lenguaje de la CPU (bueno, de la microarquitectura). La mayoría de las CPUs (y los compiladores) tenderán a optimizar el MOV ax, ax lejos; NOP siempre tendrá una cantidad fija de ciclos para su ejecución. Pero no veo cómo eso es relevante para lo que he escrito en mi respuesta de todos modos.

0 votos

Las CPUs no pueden realmente optimizar un MOV ax, ax de distancia, porque en el momento en que saben que es un MOV la instrucción ya está en la tubería.

5voto

Philip Oakley Puntos 151

A finales de los 70, nosotros (yo era un joven estudiante de investigación entonces) teníamos un pequeño sistema de desarrollo (8080 si la memoria no me falla) que funcionaba con 1024 bytes de código (es decir, una sola UVEPROM) - sólo tenía cuatro comandos para cargar (L), guardar (S), imprimir (P), y algo más que no recuerdo. Se manejaba con un teletipo real y una cinta perforada. Estaba muy bien codificado.

Un ejemplo del uso de NOOP fue en una rutina de servicio de interrupción (ISR), que estaban espaciadas en intervalos de 8 bytes. Esta rutina terminaba siendo de 9 bytes y terminaba con un (largo) salto a una dirección un poco más arriba en el espacio de direcciones. Esto significaba, dado el orden de los bytes little endian, que el byte de la dirección alta era 00h, y encajaba en el primer byte de la siguiente ISR, lo que significaba que ésta (la siguiente ISR) empezaba con NOOP, ¡sólo para que "pudiéramos" encajar el código en el espacio limitado!

Así que el NOOP es útil. Además, sospecho que era más fácil para Intel codificarla de esa manera - Probablemente tenían una lista de instrucciones que querían implementar y que comenzaba en '1', como todas las listas (eran los días de FORTRAN), por lo que el código NOOP cero se convirtió en una caída. (Nunca he visto un artículo en el que se argumente que el NOOP es una parte esencial de la teoría de las ciencias de la computación (la misma pregunta que: ¿tienen los matemáticos un op nul, distinto del cero de la teoría de grupos?)

0 votos

No todas las CPUs tienen el NOP codificado a 0x00 (aunque el 8085, el 8080 y la CPU con la que estoy más familiarizado, el Z80, lo tienen). Sin embargo, si yo estuviera diseñando un procesador, ¡ahí es donde lo pondría! Otra cosa que es útil es que la memoria suele estar inicializada a todos los 0x00, por lo que ejecutar esto como código no hará nada hasta que la CPU llegue a la memoria no puesta a cero.

0 votos

@CJDennis He explicado por qué las CPUs x86 no utilizan 0x00 para nop en mi respuesta. En resumen, ahorra en la decodificación de instrucciones - xchg ax, ax fluye naturalmente de la forma en que funciona la decodificación de la instrucción, y hace algo "tonto", así que por qué no usar eso y llamarlo nop ¿verdad? :) Solía ahorrar bastante en el silicio para la decodificación de instrucciones...

5voto

Mark0978 Puntos 495

En algunas arquitecturas, NOP se utiliza para ocupar los espacios no utilizados ranuras de retardo . Por ejemplo, si la instrucción de bifurcación no despeja la tubería, varias instrucciones posteriores se ejecutan de todos modos:

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

Pero, ¿qué pasa si no tiene ninguna instrucción útil para encajar después de la JMP ? En ese caso tendrás que utilizar NOP s.

Las ranuras de retardo no se limitan a los saltos. En algunas arquitecturas, riesgos de los datos en la tubería de la CPU no se resuelven automáticamente. Esto significa que después de cada instrucción que modifica un registro hay una ranura en la que el nuevo valor del registro aún no es accesible. Si la siguiente instrucción necesita ese valor, la ranura debe ser ocupada por una NOP :

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

Además, algunas instrucciones de ejecución condicional ( Si es verdadero-falso y similares) utilizan ranuras para cada condición, y cuando una condición particular no tiene acciones asociadas, su ranura debe ser ocupada por un 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

0 votos

+1. Por supuesto, esto sólo suele aparecer en arquitecturas que no se preocupan por la compatibilidad con versiones anteriores: si x86 intentara algo así al introducir el pipelining de instrucciones, casi todo el mundo lo tacharía simplemente de incorrecto (después de todo, acaban de actualizar su CPU y sus aplicaciones han dejado de funcionar). Así que x86 tiene para asegurarse de que la aplicación no nota cuando se añaden mejoras como esta a la CPU - hasta que llegamos a las CPUs multinúcleo de todos modos... :D

2voto

jns Puntos 449

Otro ejemplo de uso de un dos bytes NOP: http://blogs.msdn.com/b/oldnewthing/archive/2011/09/21/10214405.aspx

La instrucción MOV EDI, EDI es un NOP de dos bytes, que es el espacio suficiente para parchear una instrucción de salto para que la función pueda ser actualizada sobre la marcha. La intención es que la instrucción MOV EDI, EDI sea reemplazada por una instrucción JMP $-5 de dos bytes para redirigir el control a cinco bytes de espacio de parche que viene inmediatamente antes del inicio de la función. Cinco bytes son suficientes para una instrucción de salto completo, que puede enviar el control a la función de reemplazo instalada en otro lugar del espacio de direcciones.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X