cursos:ensamblador:arquitectura

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:arquitectura [12-01-2024 15:05] sromerocursos:ensamblador:arquitectura [20-01-2024 20:11] (actual) – [Partes del mapa de Memoria del Spectrum] sromero
Línea 47: Línea 47:
  Algunos de los registros que hemos nombrado pueden agruparse para formar registros de mayor tamaño y así poder realizar operaciones que requieran valores mayores que los que se pueden representar con 8 bits. Por ejemplo, **H** y **L** pueden formar juntos el registro **HL** con el que realizar operaciones de 16 bits concretas.  Algunos de los registros que hemos nombrado pueden agruparse para formar registros de mayor tamaño y así poder realizar operaciones que requieran valores mayores que los que se pueden representar con 8 bits. Por ejemplo, **H** y **L** pueden formar juntos el registro **HL** con el que realizar operaciones de 16 bits concretas.
  
- Veremos los registros en detalle en el próximo capítulo (así como el segundo juego de registros disponible), pero podemos hacernos a la idea de que los registros son simples variables de 8 ó 16 bits que utilizaremos en nuestros programas en ensamblador. Así, podremos cargar un valor en un registro (''LD A, 25''), sumar un registro con otro (''ADD AB''), activar o desactivar determinados bits de un registro (''SET 7, A''), etc.+ Veremos los registros en detalle en el próximo capítulo (así como el segundo juego de registros disponible), pero podemos hacernos a la idea de que los registros son simples variables de 8 ó 16 bits que utilizaremos en nuestros programas en ensamblador. Así, podremos cargar un valor en un registro (''ld a, 25''), sumar un registro con otro (''add ab''), activar o desactivar determinados bits de un registro (''set 7, a''), etc.
  
 \\  \\ 
Línea 56: Línea 56:
  
 <code z80> <code z80>
-  LD A, 10 +  ld a, 10 
-  LD B, 20 +  ld b, 20 
-  ADD AB+  add ab
 </code> </code>
  
Línea 77: Línea 77:
 Ya hemos visto qué son los registros del microprocesador. Ahora bien, en el ejemplo anterior, ¿cómo sabe el microprocesador qué tiene que hacer cuando se encuentra un comando ''LD'' o ''ADD''? Esto es tarea del microcódigo. El microcódigo del microprocesador es una definición de qué tiene que hacer el microprocesador ante cada una de las posibles órdenes que nosotros le demos. Ya hemos visto qué son los registros del microprocesador. Ahora bien, en el ejemplo anterior, ¿cómo sabe el microprocesador qué tiene que hacer cuando se encuentra un comando ''LD'' o ''ADD''? Esto es tarea del microcódigo. El microcódigo del microprocesador es una definición de qué tiene que hacer el microprocesador ante cada una de las posibles órdenes que nosotros le demos.
  
-Por ejemplo, cuando el microprocesador está ejecutando nuestro anterior programa y lee los valores numéricos correspondientes a ''LD A, 10'', el Z80 utiliza el microcódigo encargado de mover el valor 10 al registro A. Este microcódigo no es más que una secuencia de señales hardware y cambios de estados electrónicos cuyo resultado será, exactamente, activar y desactivar BITs en el registro A (que no es más que una serie de 8 biestables electrónicos que pueden estar a 0 voltios o a 5 voltios cada uno de ellos, representando el estado de los 8 bits del registro A). Lo mismo ocurrirá cuando se lea la instrucción ''LD B, 20'', sólo que se ejecutará otra porción de microcódigo que lo que hará será modificar el registro B.+Por ejemplo, cuando el microprocesador está ejecutando nuestro anterior programa y lee los valores numéricos correspondientes a ''ld a, 10'', el Z80 utiliza el microcódigo encargado de mover el valor 10 al registro A. Este microcódigo no es más que una secuencia de señales hardware y cambios de estados electrónicos cuyo resultado será, exactamente, activar y desactivar BITs en el registro A (que no es más que una serie de 8 biestables electrónicos que pueden estar a 0 voltios o a 5 voltios cada uno de ellos, representando el estado de los 8 bits del registro A). Lo mismo ocurrirá cuando se lea la instrucción ''ld b, 20'', sólo que se ejecutará otra porción de microcódigo que lo que hará será modificar el registro B.
  
 Este microcódigo está dentro del microprocesador porque sus diseñadores implementaron todas y cada una de las operaciones que puede hacer el Z80. Cuando pedimos meter un valor en un registro, leer el valor de un registro, sumar un registro con otro, escribir el valor de un registro en una dirección de memoria, saltar a otra parte del programa, etc, para cada una de esas situaciones, hay un microcódigo (implementado mediante hardware) que realiza esa tarea.  Este microcódigo está dentro del microprocesador porque sus diseñadores implementaron todas y cada una de las operaciones que puede hacer el Z80. Cuando pedimos meter un valor en un registro, leer el valor de un registro, sumar un registro con otro, escribir el valor de un registro en una dirección de memoria, saltar a otra parte del programa, etc, para cada una de esas situaciones, hay un microcódigo (implementado mediante hardware) que realiza esa tarea. 
Línea 154: Línea 154:
 <code z80> <code z80>
  ; Cargamos en BC el valor del puerto a leer  ; Cargamos en BC el valor del puerto a leer
- LD BCF7FEh+ ld bcf7FEh
    
  ; Leemos en el registro A el valor del puerto  ; Leemos en el registro A el valor del puerto
- IN A, (C)+ in a, (c)
 </code> </code>
  
Línea 191: Línea 191:
 Cada uno de estos cajones (más técnicamente, "celdillas de memoria" o "posiciones de memoria") puede contener un número de 8 bits, con un valor, por tanto, entre 0 y 255. Esto es así porque el "bus de datos" del microprocesador Z80 es de 8 bits, lo que implica que "sólo hay 8 conexiones" entre la salida de datos de la memoria y nuestro procesador. Cada uno de estos cajones (más técnicamente, "celdillas de memoria" o "posiciones de memoria") puede contener un número de 8 bits, con un valor, por tanto, entre 0 y 255. Esto es así porque el "bus de datos" del microprocesador Z80 es de 8 bits, lo que implica que "sólo hay 8 conexiones" entre la salida de datos de la memoria y nuestro procesador.
  
-El microprocesador Z80 puede acceder a cualquier posición de memoria tanto para leer como para escribir. Internamente, cuando le pedimos al microprocesador que meta en el registro A el contenido de la celdilla de memoria $1234, mediante una instrucción de ensamblador ''LD A, ($1234)''  lo que hace el microprocesador internamente es:+El microprocesador Z80 puede acceder a cualquier posición de memoria tanto para leer como para escribir. Internamente, cuando le pedimos al microprocesador que meta en el registro A el contenido de la celdilla de memoria $1234, mediante una instrucción de ensamblador ''ld a, ($1234)''  lo que hace el microprocesador internamente es:
  
 (Nota: el operador () (paréntesis abierto y cerrado) en ensamblador significa acceso a memoria). (Nota: el operador () (paréntesis abierto y cerrado) en ensamblador significa acceso a memoria).
Línea 197: Línea 197:
 \\  \\ 
 \\  \\ 
-**Ejecución de: LD A, ($1234)**+**Ejecución de: ld a, ($1234)**
 \\  \\ 
  
Línea 207: Línea 207:
   * La memoria recibe la señal de "deseo leer un dato" por esta señal de control, y mira el bus de direcciones que le conecta con el Spectrum. Mirando el estado de las 16 líneas encuentra el "00010010 00110100" ($1234). Con eso, la memoria sabe a qué "casilla" o "cajón" quiere acceder el microprocesador.   * La memoria recibe la señal de "deseo leer un dato" por esta señal de control, y mira el bus de direcciones que le conecta con el Spectrum. Mirando el estado de las 16 líneas encuentra el "00010010 00110100" ($1234). Con eso, la memoria sabe a qué "casilla" o "cajón" quiere acceder el microprocesador.
  
-  * La memoria lee el valor de la celdilla de memoria $1234 (supongamos que contiene, por ejemplo $0F, que es 00001111 en binario) y cambia las 8 conexiones del Bus de Datos para que contengan 00001111 (4 "líneas" las pone a 0 voltios, y las otras 4 a 5 voltios).+  * La memoria lee el valor de la celdilla de memoria $1234 (supongamos que contiene, por ejemplo $0f, que es 00001111 en binario) y cambia las 8 conexiones del Bus de Datos para que contengan 00001111 (4 "líneas" las pone a 0 voltios, y las otras 4 a 5 voltios).
  
   * El Z80 consulta el bus de datos y ve el estado de las líneas, con lo que lee el "00001111" o 0Fh.   * El Z80 consulta el bus de datos y ve el estado de las líneas, con lo que lee el "00001111" o 0Fh.
  
-  * El Z80 coloca en el registro A el valor $0F.+  * El Z80 coloca en el registro A el valor $0f.
 \\  \\ 
  
Línea 218: Línea 218:
 \\  \\ 
 \\  \\ 
-**Ejecución de: LD ($1234), A**+**Ejecución de: ld ($1234), a**
 \\  \\ 
  
 \\  \\ 
-  * Supongamos que A contiene el valor 15 ($0F): el Z80 coloca las líneas del bus de datos a los valores 00001111 (donde cada 0 es 0 Voltios y cada 1 son 5 Voltios).+  * Supongamos que A contiene el valor 15 ($0f): el Z80 coloca las líneas del bus de datos a los valores 00001111 (donde cada 0 es 0 Voltios y cada 1 son 5 Voltios).
  
   * El Z80 coloca las líneas del bus de direcciones a 00010010 00110100 ($1234).   * El Z80 coloca las líneas del bus de direcciones a 00010010 00110100 ($1234).
Línea 230: Línea 230:
   * La memoria recibe la señal de "deseo escribir un dato", y mira el bus de direcciones que le conecta con el Spectrum. Obteniendo el estado de las 16 líneas encuentra el "00010010 00110100" ($1234). Con eso, la memoria sabe a qué "casilla" o "cajón" quiere acceder el microprocesador para escribir.   * La memoria recibe la señal de "deseo escribir un dato", y mira el bus de direcciones que le conecta con el Spectrum. Obteniendo el estado de las 16 líneas encuentra el "00010010 00110100" ($1234). Con eso, la memoria sabe a qué "casilla" o "cajón" quiere acceder el microprocesador para escribir.
  
-  * La memoria lee el valor del bus de datos para saber qué dato tiene que escribir y obtiene el valor $0F.+  * La memoria lee el valor del bus de datos para saber qué dato tiene que escribir y obtiene el valor $0f.
  
-  * La memoria escribe en su celdilla número $1234 el valor $0F.+  * La memoria escribe en su celdilla número $1234 el valor $0f.
  
 \\  \\ 
-Estas son las 2 operaciones básicas que el Z80 puede realizar con la memoria: leer una posición de memoria y escribir en una posición de memoria. Nosotros no tenemos que preocuparnos de ninguna de las señales hardware necesarias para realizar lecturas y escrituras, de eso se encarga el microprocesador. Para nosotros, a nivel de ensamblador, nos bastará con ejecutar ''LD A, ($1234)'' o ''LD ($1234), A'', por ejemplo.+Estas son las 2 operaciones básicas que el Z80 puede realizar con la memoria: leer una posición de memoria y escribir en una posición de memoria. Nosotros no tenemos que preocuparnos de ninguna de las señales hardware necesarias para realizar lecturas y escrituras, de eso se encarga el microprocesador. Para nosotros, a nivel de ensamblador, nos bastará con ejecutar ''ld a, ($1234)'' o ''ld ($1234), a'', por ejemplo.
  
 Las celdillas de memoria desde la nº 0 a la 16383 están ocupadas por un chip que es la ''ROM'' del Spectrum. Este chip es de sólo lectura (ROM = Read Only Memory), lo cual quiere decir que si intentamos escribir en las celdillas desde la 0 a la 16383 no conseguiremos cambiar el valor almacenado en ellas. ¿Por qué no se puede escribir aquí? Porque es la ROM del Spectrum, es un chip que contiene el "sistema operativo" del Spectrum, su intérprete BASIC, como veremos posteriormente. Las celdillas de memoria desde la nº 0 a la 16383 están ocupadas por un chip que es la ''ROM'' del Spectrum. Este chip es de sólo lectura (ROM = Read Only Memory), lo cual quiere decir que si intentamos escribir en las celdillas desde la 0 a la 16383 no conseguiremos cambiar el valor almacenado en ellas. ¿Por qué no se puede escribir aquí? Porque es la ROM del Spectrum, es un chip que contiene el "sistema operativo" del Spectrum, su intérprete BASIC, como veremos posteriormente.
Línea 288: Línea 288:
 \\  \\ 
  
-**Bloque desde  0 ($0000) a 16383 ($3FFF)**:\\ +**Bloque desde  0 ($0000) a 16383 ($3fff)**:\\ 
 Memoria ROM, de solo lectura. En total son 16384 bytes (16KBytes). Memoria ROM, de solo lectura. En total son 16384 bytes (16KBytes).
  
-**Bloque desde 16384 ($4000) a 22527 ($57FF)**:\\ +**Bloque desde 16384 ($4000) a 22527 ($57ff)**:\\ 
 "Fichero de pantalla" (VideoRAM) - parte 1: Definición de pixels en pantalla. "Fichero de pantalla" (VideoRAM) - parte 1: Definición de pixels en pantalla.
 Un byte por línea de 8 pixels definiendo cuál está encendido o no. En total son 6144 bytes (6KB) Un byte por línea de 8 pixels definiendo cuál está encendido o no. En total son 6144 bytes (6KB)
  
-**Bloque desde 22528 ($5800) a 23295 ($5AFF)**:\\ +**Bloque desde 22528 ($5800) a 23295 ($5aff)**:\\ 
 "Fichero de pantalla" (VideoRAM) - parte 2: Definición de atributos de color en pantalla. "Fichero de pantalla" (VideoRAM) - parte 2: Definición de atributos de color en pantalla.
 Un byte por cuadro de 8x8 pixels. Un byte por cuadro de 8x8 pixels.
Línea 301: Línea 301:
 En total son 768 bytes. En total son 768 bytes.
  
-**Bloque desde 23296 ($5B00) a 23551 ($5BFF)**:\\ +**Bloque desde 23296 ($5b00) a 23551 ($5bff)**:\\ 
 Buffer de impresora. Son 256 bytes que almacenan temporalmente lo que se imprimirá. Buffer de impresora. Son 256 bytes que almacenan temporalmente lo que se imprimirá.
 Si no tenemos impresora (o nuestro programa no la empleará) podemos aprovechar estos Si no tenemos impresora (o nuestro programa no la empleará) podemos aprovechar estos
 256 bytes para almacenar una pequeña rutina en código máquina o la pila del programa. 256 bytes para almacenar una pequeña rutina en código máquina o la pila del programa.
  
-**Bloque desde 23552 ($5C00) a 23733 ($5CB5)**:\\ +**Bloque desde 23552 ($5c00) a 23733 ($5cb5)**:\\ 
 Variables del sistema. Contienen varios elementos de información que indican al ordenador Variables del sistema. Contienen varios elementos de información que indican al ordenador
 en qué estado se halla y son el objetivo de este documento. Algunas de ellas establecen en qué estado se halla y son el objetivo de este documento. Algunas de ellas establecen
 los límites de las próximas divisiones que veremos en este mismo mapa de memoria los límites de las próximas divisiones que veremos en este mismo mapa de memoria
  
-**Bloque desde 23734 ($5CB6) a 24576 ($6000)**:\\ +**Bloque desde 23734 ($5cb6) a 24576 ($6000)**:\\ 
 A partir de aquí, hasta aproximadamente 24576, tenemos ciertas variables del sistema y A partir de aquí, hasta aproximadamente 24576, tenemos ciertas variables del sistema y
 porciones de memoria usadas por BASIC como: porciones de memoria usadas por BASIC como:
Línea 331: Línea 331:
 En general, podemos utilizar la memoria desde aquí en adelante para nuestros programas. En general, podemos utilizar la memoria desde aquí en adelante para nuestros programas.
  
-En un modelo 16K, toda la RAM libre y disponible es aquella que queda entre $6000 (24574) y $7FFF (32767), es decir, apenas 8KB de memoria RAM para código, gráficos y sonidos de los programas, motivo por el cual los juegos de 16KB no podían ser demasiado complejos.+En un modelo 16K, toda la RAM libre y disponible es aquella que queda entre $6000 (24574) y $7fff (32767), es decir, apenas 8KB de memoria RAM para código, gráficos y sonidos de los programas, motivo por el cual los juegos de 16KB no podían ser demasiado complejos.
  
-En un modelo 48K, encontraremos después de $6000 mucha más RAM. Aparte de los 8KB entre $6000 y $7FFF, tenemos otros 2 bloques de 16KB (un total de 32KB más) de $8000 (32768) a $BFFF (49151) y de $C000 (49152) a $FFFF (65535).+En un modelo 48K, encontraremos después de $6000 mucha más RAM. Aparte de los 8KB entre $6000 y $7fff, tenemos otros 2 bloques de 16KB (un total de 32KB más) de $8000 (32768) a $bfff (49151) y de $c000 (49152) a $ffff (65535). 
 + 
 +La siguiente figura, extraída del libro "//Sprites y Graficos En Lenguaje Maquina//" (de //John Durst//), muestra un esquema del mapa de memoria del Spectrum 48K: 
 + 
 +\\  
 +{{ :cursos:ensamblador:mapa-mem-sprites-y-graficos-lenguajemaquina.jpg }} 
 +\\ 
  
 \\  \\ 
 **¿Dónde están los UDG?** **¿Dónde están los UDG?**
  
-El valor de la dirección de memoria **23675** (**$5C7B** - variable del sistema "''UDG''") contiene la dirección de memoria donde están los gráficos definidos por el usuario. Por defecto en un Spectrum 48K, esta variable apunta a la dirección de memoria 65368, lo que deja 65535-65368 = 168 bytes para UDGs, que son (168/8) un total de 21 UDGs.+El valor de la dirección de memoria **23675** (**$5c7b** - variable del sistema "''UDG''") contiene la dirección de memoria donde están los gráficos definidos por el usuario. Por defecto en un Spectrum 48K, esta variable apunta a la dirección de memoria 65368, lo que deja 65535-65368 = 168 bytes para UDGs, que son (168/8) un total de 21 UDGs.
  
-Para establecer nuestros propios UDGs, podemos pues POKEar datos gráficos en esas direcciones, o simplemente definir esos gráficos en nuestro programa y cambiar la variable UDG para apuntar a la dirección de memoria donde están definidos en nuestro código. Una vez que lo tengamos, al pintar con nuestro ya conocido **RST 16** cualquier carácter comprendido entre $90 y $A4, dibujará los gráficos que hemos definido en nuestro área de memoria a tal efecto.+Para establecer nuestros propios UDGs, podemos pues POKEar datos gráficos en esas direcciones, o simplemente definir esos gráficos en nuestro programa y cambiar la variable UDG para apuntar a la dirección de memoria donde están definidos en nuestro código. Una vez que lo tengamos, al pintar con nuestro ya conocido **rst 16** cualquier carácter comprendido entre $90 y $a4, dibujará los gráficos que hemos definido en nuestro área de memoria a tal efecto.
  
  
Línea 387: Línea 393:
     * Encendido de ordenador:     * Encendido de ordenador:
           o PC (el Contador de Programa) se pone a 0.           o PC (el Contador de Programa) se pone a 0.
-          o SP se pone a $FFFF.+          o SP se pone a $ffff.
     * Mientras No Se Apague el Ordenador:     * Mientras No Se Apague el Ordenador:
       Leer de la memoria la siguiente instrucción, mediante        Leer de la memoria la siguiente instrucción, mediante 
Línea 403: Línea 409:
           o Ejecutar la instrucción           o Ejecutar la instrucción
      Fin Mientras      Fin Mientras
-     
 </code> </code>
  
Línea 410: Línea 415:
 <code> <code>
  PC              = $0000  PC              = $0000
- AF, SP          = $FFFF+ AF, SP          = $ffff
  Resto           = Valor indefinido  Resto           = Valor indefinido
  Interrupciones  Interrupciones
Línea 429: Línea 434:
 ==== Opcodes y código máquina ==== ==== Opcodes y código máquina ====
  
-Nuestro microprocesador Z80 no entiende los comandos en ensamblador que hemos estado viendo en estos 2 primeros capítulos del curso de código máquina; el Z80 sólo entiende números binarios, números de 8 bits de 0 a 255 (o de $00 a $FF en hexadecimal).+Nuestro microprocesador Z80 no entiende los comandos en ensamblador que hemos estado viendo en estos 2 primeros capítulos del curso de código máquina; el Z80 sólo entiende números binarios, números de 8 bits de 0 a 255 (o de $00 a $ff en hexadecimal).
  
 De entre los registros del microprocesador hay uno llamado PC (Program Counter o Contador de Programa), que como ya hemos visto, es el "puntero" que apunta a la instrucción actual que se está ejecutando. Cuando ejecutamos un programa, lo que hacemos es meterlo en memoria (por ejemplo, como cuando en la primera entrega del curso POKEábamos nuestra rutina a partir de la dirección 40.000) y después saltar al inicio del mismo. De entre los registros del microprocesador hay uno llamado PC (Program Counter o Contador de Programa), que como ya hemos visto, es el "puntero" que apunta a la instrucción actual que se está ejecutando. Cuando ejecutamos un programa, lo que hacemos es meterlo en memoria (por ejemplo, como cuando en la primera entrega del curso POKEábamos nuestra rutina a partir de la dirección 40.000) y después saltar al inicio del mismo.
Línea 436: Línea 441:
  
 <code z80> <code z80>
- LD A, 0 + ld a, 0 
- INC A + inc a 
- LD B, $FFh + ld b, $ffh 
- INC B + inc b 
- LD DE, $AABB + ld de, $aabb 
- RET+ ret
 </code> </code>
  
Línea 469: Línea 474:
 |< 50% 33% 33% 33% >| |< 50% 33% 33% 33% >|
 ^ Dirección ^ Valor hexadecimal ^ Significado ^ ^ Dirección ^ Valor hexadecimal ^ Significado ^
-| 40000 | 3e | LD A, |+| 40000 | 3e | ld a, |
 | 40001 | 00 | 00h | | 40001 | 00 | 00h |
-| 40002 | 3c | INC A +| 40002 | 3c | inc a 
-| 40003 | 06 | LD B, |+| 40003 | 06 | ld b, |
 | 40004 | ff | FFh | | 40004 | ff | FFh |
-| 40005 | 04 | INC B +| 40005 | 04 | inc b 
-| 40006 | 11 | LD DE, |+| 40006 | 11 | ld de, |
 | 40007 | bb | BBh | | 40007 | bb | BBh |
 | 40008 | aa | AAh | | 40008 | aa | AAh |
-| 40009 | c9 | RET |+| 40009 | c9 | ret |
  
-A la hora de ejecutar el programa, nuestro RANDOMIZE USR 40000 lo que hace en realidad es cambiar el valor del registro "PC" del microprocesador. Hace PC igual a 40000. Así, una vez realizamos el salto a la dirección 40000, el microprocesador hace lo siguiente:+A la hora de ejecutar el programa, nuestro ''RANDOMIZE USR 40000'' lo que hace en realidad es cambiar el valor del registro "PC" del microprocesador. Hace PC igual a 40000. Así, una vez realizamos el salto a la dirección 40000, el microprocesador hace lo siguiente:
  
 <code> <code>
     * Leer el byte contenido en la dirección de memoria "PC" (40000).     * Leer el byte contenido en la dirección de memoria "PC" (40000).
     * Incrementar PC (PC=PC+1).     * Incrementar PC (PC=PC+1).
-    * El byte es "$3e", con lo cual el Spectrum sabe que +    * El byte es "$3e", con lo cual el Spectrum sabe que el opcode 
-      tiene que meter en A un valor numérico. +      la instrucción es un "ld a, valor", y que por tanto tiene 
-    * El valor extra para "LD A," está a continuación en memoria, +      que meter en A un valor numérico. 
 +    * El valor extra para "ld a," está a continuación en memoria, 
       así que se lee la memoria de nuevo:       así que se lee la memoria de nuevo:
           o operando = [PC] = $00           o operando = [PC] = $00
           o Incrementar PC (PC=PC+1)            o Incrementar PC (PC=PC+1) 
     * Ya se tiene el "código de instrucción completo", así que se      * Ya se tiene el "código de instrucción completo", así que se 
-      ejecuta: "LD A, 00". (se ejecuta el microcódigo correspondiente +      ejecuta: "ld a, 00". (se ejecuta el microcódigo correspondiente 
       dentro de la CPU).       dentro de la CPU).
 </code> </code>
  
-Esto que hemos visto es el proceso de "Lectura de Instrucción (fetch)", "decodificación (decode)", y "ejecución (execute)". Pero recordemos que este proceso se ejecuta una y otra vez, sin parar, de modo que el procesador sigue con la siguiente instrucción (INC A):+Esto que hemos visto es el proceso de "Lectura de Instrucción (fetch)", "decodificación (decode)", y "ejecución (execute)". Pero recordemos que este proceso se ejecuta una y otra vez, sin parar, de modo que el procesador sigue con la siguiente instrucción (''inc a''):
  
 <code> <code>
Línea 502: Línea 508:
     * Incrementar PC (PC=PC+1).     * Incrementar PC (PC=PC+1).
     * El byte es "$3c", con lo cual el Spectrum sabe que tiene que incrementar A.     * El byte es "$3c", con lo cual el Spectrum sabe que tiene que incrementar A.
-    * No hacen falta operandos extra, INC A no requiere nada más. +    * No hacen falta operandos extra, inc a no requiere nada más. 
-    * Ya se tiene el "código de instrucción completo", así que se ejecuta: "INC A".+    * Ya se tiene el "código de instrucción completo", así que se ejecuta: "inc a".
 </code> </code>
  
-Y este ciclo se vuelve a repetir, una y otra vez, hasta que llegamos al RET:+Y este ciclo se vuelve a repetir, una y otra vez, hasta que llegamos al ret:
  
 <code> <code>
     * Leer el byte contenido en la dirección de memoria "PC" (40009).     * Leer el byte contenido en la dirección de memoria "PC" (40009).
     * Incrementar PC (PC=PC+1).     * Incrementar PC (PC=PC+1).
-    * El byte es "$c9", con lo cual el Spectrum sabe que tiene que hacer un RET+    * El byte es "$c9", con lo cual el Spectrum sabe que tiene que hacer un "ret"
-    * No hacen falta operandos extra, RET no requiere nada más. +    * No hacen falta operandos extra, ret no requiere nada más. 
-    * Ya se tiene el "código de instrucción completo", así que se ejecuta: "RET".+    * Ya se tiene el "código de instrucción completo", así que se ejecuta: "ret".
 </code> </code>
  
Línea 520: Línea 526:
     * Como véis, el microprocesador no entiende el lenguaje ensamblador, sólo la traducción de este a Lenguaje Máquina (los números u opcodes que estamos viendo).     * Como véis, el microprocesador no entiende el lenguaje ensamblador, sólo la traducción de este a Lenguaje Máquina (los números u opcodes que estamos viendo).
  
-    * La primera parte leída de la instrucción es el OPCODE (código de operación), y es lo que permite al Spectrum, mediante una "tabla interna", saber qué tarea exacta tiene que realizar. Si la instrucción necesita datos extra para leer de memoria, se almacenan tras el opcode, y se conocen como "operandos". Así, ''LD A, 00'' se corresponde con la instrucción **$3E $00**, donde **$3E** es el código de operación (opcode) y **$00** es el operando.+    * La primera parte leída de la instrucción es el OPCODE (código de operación), y es lo que permite al Spectrum, mediante una "tabla interna", saber qué tarea exacta tiene que realizar. Si la instrucción necesita datos extra para leer de memoria, se almacenan tras el opcode, y se conocen como "operandos". Así, ''ld a, 00'' se corresponde con la instrucción **$3e $00**, donde **$3e** es el código de operación (opcode) y **$00** es el operando.
  
-    * Para el Spectrum, no hay diferencia entre instrucciones y datos. Un "$3C" puede ser un ''INC A'' o un valor númerico "$3C". ¿Cómo distingue el Spectrum uno de otro? Sencillo: todo depende de si se encuentra al principio de un ciclo de decodificación o no. Es decir, si cuando vamos a empezar a leer una instrucción leemos un "$3C", es un ''INC A''. Pero si lo leemos en el proceso de lectura de un operando, su significado cambia. Pensad en por ejemplo en ''LD A, $3C'', que se codificaría como "$3E $3C", pero no ejecutaría un INC A porque la lectura del "$3C" se realiza como operando para el ''LD A,''.+    * Para el Spectrum, no hay diferencia entre instrucciones y datos. Un **$3c** puede ser un ''inc a'' o un valor númerico **$3c** como parte de una cadena de texto, o de un gráfico, o un operador (el valor que queremos meter en un registro). ¿Cómo distingue el Spectrum uno de otro? Sencillo: todo depende de si se encuentra al principio de un ciclo de decodificación o no. Es decir, si cuando vamos a empezar a leer una instrucción, cuando estamos leyendo el opcode de la misma, leemos un **$3c**, es un ''inc a''. Pero si lo leemos en el proceso de lectura de un operando, su significado cambia. Pensad en por ejemplo en ''ld a, $3c'', que se codificaría como **$3e $3c**, pero no ejecutaría un ''inc a'' porque la lectura del **$3c** se realiza como operando para el ''ld a,''.
  
     * Al no existir diferencia entre instrucciones y datos, si cambiamos PC de forma que apunte a una zona de la memoria donde hay datos y no código, el Z80 tratará de interpretar los números que va leyendo como si fueran opcodes (con resultados imprecedibles, seguramente con el cuelgue del Spectrum o un reset).     * Al no existir diferencia entre instrucciones y datos, si cambiamos PC de forma que apunte a una zona de la memoria donde hay datos y no código, el Z80 tratará de interpretar los números que va leyendo como si fueran opcodes (con resultados imprecedibles, seguramente con el cuelgue del Spectrum o un reset).
  
-    * Por último, existen una serie de opcodes compuestos (dejando de lado los operandos) que ocupan más de 1 byte. Esos opcodes suelen comenzar por CB, ED o FD, de forma que, por ejemplo el opcode "$CB $04se corresponde con la operación ''RLC L''. Si sólo pudieramos utilizar un byte para representar el opcode, sólo tendríamos disponibles 256 posibles instrucciones en el procesador. Para poder disponer de más instrucciones se utilizan códigos de instrucción de más de un byte. Así, cuando nuestro procesador encuentra un CB, ED o FD sabe que el próximo código que lea después tendrá un significado diferente al que tendría sin el CB, ED o FD delante. Es por eso que "$04significa ''INC B'',"$CB $04significa ''RLC L'' (una instrucción diferente).+    * Por último, existen una serie de opcodes compuestos (dejando de lado los operandos) que ocupan más de 1 byte. Esos opcodes suelen comenzar por CB, ED o FD, de forma que, por ejemplo el opcode **$cb $04** se corresponde con la operación ''rlc l''. Si sólo pudieramos utilizar un byte para representar el opcode, sólo tendríamos disponibles 256 posibles instrucciones en el procesador. Para poder disponer de más instrucciones se utilizan códigos de instrucción de más de un byte. Así, cuando nuestro procesador encuentra un CB, ED o FD sabe que el próximo código que lea después tendrá un significado diferente al que tendría sin el CB, ED o FD delante. Es por eso que **$04** significa ''inc b'',**$cb $04** significa ''rlc l'' (una instrucción diferente).
  
-    * Cuando un operando es de 16 bits (2 bytes), primero encontramos el byte bajo y luego el byte alto. Así, nuestro ''LD DE, $AABB'' no se codifica como "$11 $AA $BB" sino como "$11 $BB $AA". El opcode para ''LD DE'' es "$11", y "$BB $AA" los operandos (en este caso, un valor numérico directo). Esta forma de almacenamiento se denomina técnicamente "little endian", como veremos en el siguiente apartado.+    * Cuando un operando es de 16 bits (2 bytes), primero encontramos el byte bajo y luego el byte alto. Así, nuestro ''ld de, $aabb'' no se codifica como **$11 $aa $bb** sino como **$11 $bb $aa**. El opcode para ''LD DE'' es **$11**, y **$bb $aa** los operandos (en este caso, un valor numérico directo). Esta forma de almacenamiento se denomina técnicamente "little endian", como veremos en el siguiente apartado.
  
 \\  \\ 
Línea 540: Línea 546:
  
 <code z80> <code z80>
-ORG 50000+    ORG 50000
  
-LD HL, $4000   ; 21 00 40 +    ld hl, $4000   ; 21 00 40 
-RET            ; C9+    ret            ; C9
 </code> </code>
  
-Teniendo en cuenta que el opcode de ''LD HL, NNNN'' es "**$21 NN NN**", y el opcode de ''RET'' es **$C9**, el programa aparecerá en memoria así:+Teniendo en cuenta que el opcode de ''ld hl, NNNN'' es "**$21 NN NN**", y el opcode de ''RET'' es **$c9**, el programa aparecerá en memoria así:
  
 |< 40% 50% 50% >| |< 40% 50% 50% >|
Línea 553: Línea 559:
 | 50001 | $00 | | 50001 | $00 |
 | 50002 | $40 | | 50002 | $40 |
-| 50003 | $C9 |+| 50003 | $c9 |
  
-Si ahora saltamos a la dirección 50000 para ejecutar el programa, lo primero que encuentra el procesador es $21, que indica al procesador que la instrucción es un ''LD HL,'' y que el valor a cargar en HL viene en memoria a continuación en las 2 siguientes celdillas de memoria. Pero al ser un procesador little-endian, no nos encontramos primero $40 y luego $00 (como en el $4000 que nosotros tecleamos en nuestro programa ensamblador) sino que en memoria está almacenado al revés: $00 primero y $40 en el byte siguiente. Cuando el procesador haya terminado de leer los 3 bytes y ejecute la instrucción, el registro HL contendrá el valor $4000 (no $0040, ya que el orden de aparición sólo es una cuestión de orden de almacenamiento).+Si ahora saltamos a la dirección 50000 para ejecutar el programa, lo primero que encuentra el procesador es $21, que indica al procesador que la instrucción es un ''ld hl,'' y que el valor a cargar en HL viene en memoria a continuación en las 2 siguientes celdillas de memoria. Pero al ser un procesador little-endian, no nos encontramos primero **$40** y luego **$00** (como en el **$4000** que nosotros tecleamos en nuestro programa ensamblador) sino que en memoria está almacenado al revés: **$00** primero y **$40** en el byte siguiente. Cuando el procesador haya terminado de leer los 3 bytes y ejecute la instrucción, el registro HL contendrá el valor $4000 (no $0040, ya que el orden de aparición sólo es una cuestión de orden de almacenamiento).
  
 Esto no sólo ocurre con las instrucciones ensambladas, sino que ocurre lo mismo con los datos de 16 bits almacenados en memoria. Si ejecutamos: Esto no sólo ocurre con las instrucciones ensambladas, sino que ocurre lo mismo con los datos de 16 bits almacenados en memoria. Si ejecutamos:
  
 <code z80> <code z80>
-LD BC, $1234 +    ld bc, $1234 
-LD ($4000), BC+    ld ($4000), bc
 </code> </code>
  
-Si revisamos los valores almacenados en memoria, encontraremos el valor $34 en $4000 y el valor $12 en $4001.+Si revisamos los valores almacenados en memoria, encontraremos el valor **$34** en **$4000** y el valor **$12** en **$4001**.
  
 Y si después hacemos: Y si después hacemos:
  
 <code z80> <code z80>
-LD DE, ($4000)+    ld de, ($4000)
 </code> </code>
  
 El valor en DE será $1234, el valor correcto tal y como lo escribimos en nuestro programa, y no el valor al revés. De igual forma que el dato escribió como "parte-baja" y después "parte-alta", cuando el Z80 lo lee, también lo lee correctamente para obtener el valor de 16 bits. Es decir, el orden en que el Z80 lee y escribe los 2 bytes es en realidad transparente para nosotros. El valor en DE será $1234, el valor correcto tal y como lo escribimos en nuestro programa, y no el valor al revés. De igual forma que el dato escribió como "parte-baja" y después "parte-alta", cuando el Z80 lo lee, también lo lee correctamente para obtener el valor de 16 bits. Es decir, el orden en que el Z80 lee y escribe los 2 bytes es en realidad transparente para nosotros.
  
-Donde sí debemos tener nosotros en cuenta este orden es a la hora de recuperar valores de 16 bits de memoria con las operaciones de 8 bits del tipo ''LD A, (HL)''. Si por ejemplo necesitamos acceder sólo a la parte baja de un dato de 16 bits, debemos saber que el primero de los 2 bytes que encontraremos será la parte baja y no la parte alta del valor.+Donde sí debemos tener nosotros en cuenta este orden es a la hora de recuperar valores de 16 bits de memoria con las operaciones de 8 bits del tipo ''ld a, (hl)''. Si por ejemplo necesitamos acceder sólo a la parte baja de un dato de 16 bits, debemos saber que el primero de los 2 bytes que encontraremos será la parte baja y no la parte alta del valor.
  
 En los procesadores del tipo //BIG-ENDIAN//, los bytes aparecerían escritos en memoria al revés, como $40 y $00. En los procesadores del tipo //BIG-ENDIAN//, los bytes aparecerían escritos en memoria al revés, como $40 y $00.
Línea 592: Línea 598:
 ===== Tiempos de ejecución ===== ===== Tiempos de ejecución =====
  
-Cada instrucción necesita un tiempo diferente para ejecutarse. No es lo mismo un simple ''INC A'', que requiere leer un único byte como opcode, no requiere parámetros, y sólo realiza un incremento en un registro, que un complejo ''LD A, ($1234)'', que requiere leer el opcode, a continuación leer 2 bytes para el operando "$1234", después acceder a la memoria y extraer el dato contenido en ($1234) para, finalmente, depositarlo en A.+Cada instrucción necesita un tiempo diferente para ejecutarse. No es lo mismo un simple ''inc a'', que requiere leer un único byte como opcode, no requiere parámetros, y sólo realiza un incremento en un registro, que un complejo ''ld a, ($1234)'', que requiere leer el opcode, a continuación leer 2 bytes para el operando "$1234", después acceder a la memoria y extraer el dato contenido en ($1234) para, finalmente, depositarlo en A.
  
 Los tiempos de ejecución de cada instrucción son, pues, diferentes, y para conocerlos tendremos que consultar cualquier tabla de tiempos, medidos en t-states o t-estados. El t-estado o "ciclo del procesador", es la unidad de medida básica de tiempo. Podéis acceder a alguna de estas tablas en los enlaces que veréis al final de este artículo. Los tiempos de ejecución de cada instrucción son, pues, diferentes, y para conocerlos tendremos que consultar cualquier tabla de tiempos, medidos en t-states o t-estados. El t-estado o "ciclo del procesador", es la unidad de medida básica de tiempo. Podéis acceder a alguna de estas tablas en los enlaces que veréis al final de este artículo.
Línea 601: Línea 607:
  
 <code z80> <code z80>
-  LD A, 0         ; A = 0+  ld a, 0         ; A = 0
 </code> </code>
  
-Pone en A el valor 0 y ocupa 2 bytes (**$3E $00**) y tarda 7 ciclos de reloj en ejecutarse.+Pone en A el valor 0 y ocupa 2 bytes (**$3e $00**) y tarda 7 ciclos de reloj en ejecutarse.
  
 Sin embargo, la siguiente operación: Sin embargo, la siguiente operación:
  
 <code z80> <code z80>
-  XOR A           ; A = A XOR A = 0+  xor a           ; A = A xor a = 0
 </code> </code>
  
-Realiza la misma acción (hacer un ''XOR'' de un valor con el mismo valor siempre da 0, como veremos más adelante), pero ocupando un sólo byte (**$AF**) en el programa (y por tanto en la memoria) y se ejecuta en 4 ciclos de reloj, casi el doble de rápido.+Realiza la misma acción (hacer un ''XOR'' de un valor con el mismo valor siempre da 0, como veremos más adelante), pero ocupando un sólo byte (**$af**) en el programa (y por tanto en la memoria) y se ejecuta en 4 ciclos de reloj, casi el doble de rápido.
  
 Utilizando este tipo de trucos a lo largo del programa, en programas grandes acabamos ahorrando mucho espacio y consiguiendo que el programa sea más rápido. Utilizando este tipo de trucos a lo largo del programa, en programas grandes acabamos ahorrando mucho espacio y consiguiendo que el programa sea más rápido.
  • cursos/ensamblador/arquitectura.1705071913.txt.gz
  • Última modificación: 12-01-2024 15:05
  • por sromero