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 [06-01-2024 14:42] – [Enlaces] sromerocursos:ensamblador:arquitectura [20-01-2024 20:11] (actual) – [Partes del mapa de Memoria del Spectrum] sromero
Línea 16: Línea 16:
 \\  \\ 
  
-En un vistazo general, podemos ver que el microprocesador Z80 se conecta mediante los puertos de entrada/salida de la CPU a los periféricos externos (teclado, cassette y altavoz de audio), pudiendo leer el estado de los mismos (leer del teclado, leer del cassette) y escribir en ellos (escribir en el altavoz para reproducir sonido, escribir en el cassette) por medio de estas conexiones conocidas como "I/O Ports".+En un vistazo general, podemos ver que el microprocesador Z80 se conecta mediante los puertos de entrada/salida de la CPU a los periféricos externos (teclado, cassette y altavoz de audio), pudiendo leer el estado de los mismos (leer del teclado, leer del cassette) y escribir en ellos (escribir en el altavoz para reproducir sonido, escribir en el cassette) por medio de estas conexiones conocidas como "//I/O Ports//" o "//Puertos de Entrada/Salida//".
  
 Al mismo tiempo, los Buses de Datos y de Direcciones conectan al microprocesador con la memoria. Esta conexión es la que permite que el Z80 pueda leer y escribir en cualquier posición de la RAM, y leer datos de la ROM (que, juntas, conforman la totalidad de la memoria disponible). Al mismo tiempo, los Buses de Datos y de Direcciones conectan al microprocesador con la memoria. Esta conexión es la que permite que el Z80 pueda leer y escribir en cualquier posición de la RAM, y leer datos de la ROM (que, juntas, conforman la totalidad de la memoria disponible).
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 71: Línea 71:
 Cuando tratemos las diferentes instrucciones del Z80 veremos en más detalle los registros, su tamaño, cómo se agrupan, y de qué forma podemos usarlos para operar entre ellos y realizar nuestras rutinas o programas. Cuando tratemos las diferentes instrucciones del Z80 veremos en más detalle los registros, su tamaño, cómo se agrupan, y de qué forma podemos usarlos para operar entre ellos y realizar nuestras rutinas o programas.
  
-Finalmente, existe un registro especial del procesador llamado PC (Program Counter, o Contador de Programa). Este registro es de 16 bits (puede contener un valor entre 0 y 65535), y su utilidad es la de apuntar a la dirección de memoria de la siguiente instrucción a ejecutar. Así, cuando arrancamos nuestro Spectrum, el registro PC vale $0000, con lo que lo primero que se ejecuta en el Spectrum es el código que hay en $0000. Una vez leído y ejecutado ese primer código de instrucción, se incrementa PC para apuntar al siguiente, y así continuadamente. +Finalmente, existe un registro especial del procesador llamado **PC** (//Program Counter//, o //Contador de Programa//). Este registro es de 16 bits (puede contener un valor entre 0 y 65535), y su utilidad es la de apuntar a la dirección de memoria de la siguiente instrucción a ejecutar. Así, cuando arrancamos nuestro Spectrum, el registro PC vale $0000, con lo que lo primero que se ejecuta en el Spectrum es el código que hay en $0000. Una vez leído y ejecutado ese primer código de instrucción, se incrementa PC para apuntar al siguiente, y así continuadamente. 
  
 Los programas se ejecutan linealmente mediante un ciclo basado en: Leer instrucción en la dirección de memoria apuntada por PC, incrementar registro PC, ejecutar instrucción. Posteriormente veremos más acerca de PC. Los programas se ejecutan linealmente mediante un ciclo basado en: Leer instrucción en la dirección de memoria apuntada por PC, incrementar registro PC, ejecutar instrucción. Posteriormente veremos más acerca de PC.
  
-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"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'' ''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 96: Línea 96:
 Pero nuestro procesador, como hemos dicho, necesita conectarse con elementos del exterior, entre ellos (y casi exclusivamente en el caso del Spectrum) tenemos la memoria, el teclado, el altavoz y la unidad de cinta. Pero nuestro procesador, como hemos dicho, necesita conectarse con elementos del exterior, entre ellos (y casi exclusivamente en el caso del Spectrum) tenemos la memoria, el teclado, el altavoz y la unidad de cinta.
  
-Visto de un modo muy simple, el Z80 puede acceder a través de una serie de patillas a múltiples elementos externos. Esos elementos se conectan a la CPU Z80 a través de las patillas de "Buses de datos y direcciones(que podemos leer en los puertos de Entrada/Salida). Una vez conectados (la conexión se realiza físicamente a nivel de hardware, con pistas en la placa base del Spectrum), el microprocesador Z80 puede leer los valores que el dispositivo pone en esas pistas, o puede escribir valores en esas pistas para que el dispositivo los utilice.+Visto de un modo muy simple, el Z80 puede acceder a través de una serie de patillas a múltiples elementos externos. Esos elementos se conectan a la CPU Z80 a través de las patillas de Buses de datos y direcciones (que podemos leer en los puertos de Entrada/Salida). Una vez conectados (la conexión se realiza físicamente a nivel de hardware, con pistas en la placa base del Spectrum), el microprocesador Z80 puede leer los valores que el dispositivo pone en esas pistas, o puede escribir valores en esas pistas para que el dispositivo los utilice.
  
  Supongamos el ejemplo del teclado: un teclado es (a un nivel muy simple) una matriz de pulsadores. Al final de toda esa matriz de pulsadores, lo que acabamos teniendo es un valor numérico cuyos bits nos indican la teclas pulsadas. Pues bien, esos bits es lo que el mismo teclado pone en los "cables" que lo unen con el Z80, concretamente en el bus de datos.  Supongamos el ejemplo del teclado: un teclado es (a un nivel muy simple) una matriz de pulsadores. Al final de toda esa matriz de pulsadores, lo que acabamos teniendo es un valor numérico cuyos bits nos indican la teclas pulsadas. Pues bien, esos bits es lo que el mismo teclado pone en los "cables" que lo unen con el Z80, concretamente en el bus de datos.
Línea 114: Línea 114:
 </code> </code>
  
- Este ejemplo lee (en un bucle infinito) una de las diferentes filas del teclado, mediante la lectura del puerto 63486 (F7FEh) con la instrucción de lectura de puertos de BASIC "IN". El estado de las 5 teclas que van desde el "1" hasta el "5" del teclado del Spectrum está conectado a 5 "hilos" de los 8 que llegan al puerto 63486 del micro Z80. + Este ejemplo lee (en un bucle infinito) una de las diferentes filas del teclado, mediante la lectura del puerto 63486 (F7FEh) con la instrucción de lectura de puertos de BASIC ''IN''. El estado de las 5 teclas que van desde el "1" hasta el "5" del teclado del Spectrum está conectado a 5 "hilos" de los 8 que llegan al puerto 63486 del micro Z80. 
  
  Si no hay ninguna tecla pulsada, el estado de estos 5 bits será un "1" lógico, que a nivel de hardware serán 5 Voltios. Cuando se pulsa una tecla, el teclado pone a "0" lógico (a 0 voltios) el "hilo" correspondiente de esos 8, y nosotros podemos conocer el estado de la tecla leyendo dicho puerto y mirando el bit en cuestión.   Si no hay ninguna tecla pulsada, el estado de estos 5 bits será un "1" lógico, que a nivel de hardware serán 5 Voltios. Cuando se pulsa una tecla, el teclado pone a "0" lógico (a 0 voltios) el "hilo" correspondiente de esos 8, y nosotros podemos conocer el estado de la tecla leyendo dicho puerto y mirando el bit en cuestión. 
Línea 123: Línea 123:
  
 \\  \\ 
-{{ cursos:ensamblador:leer_teclado.gif |Pulsando "1", ponemos a 0 el bit 0, pasando de 191 a 190}}+{{ cursos:ensamblador:leer_teclado.gif?640 |Pulsando "1", ponemos a 0 el bit 0, pasando de 191 a 190}}
 \\  \\ 
  
Línea 150: Línea 150:
 A la hora de leer estos puertos, el bit menos significativo (D0) siempre hace referencia a la tecla más alejada del centro del teclado ("1" en nuestro ejemplo), mientras que el más significativo de los 5 (D5) lo hace a la tecla más cercana al centro del teclado. A la hora de leer estos puertos, el bit menos significativo (D0) siempre hace referencia a la tecla más alejada del centro del teclado ("1" en nuestro ejemplo), mientras que el más significativo de los 5 (D5) lo hace a la tecla más cercana al centro del teclado.
  
-En ensamblador también hay disponibles 2 instrucciones para leer el contenido de un puerto de Entrada/Salida y para escribir un valor en un puerto determinado, las instrucciones se llaman igual que en BASIC: IN y OUT:+En ensamblador también hay disponibles 2 instrucciones para leer el contenido de un puerto de Entrada/Salida y para escribir un valor en un puerto determinado, las instrucciones se llaman igual que en BASIC: ''IN'' ''OUT'':
  
 <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 168: Línea 168:
  
 \\  \\ 
-El lector debería extraer una conclusión adicional del ejemplo del teclado: la gran diferencia de proceso que hay entre programar en ensamblador y programar en BASIC. Supongamos que nos interesa leer el estado del teclado para saber si unas determinadas teclas están pulsadas o no. Para esto, en ensamblador (aunque esto también podemos hacerlo en BASIC) leemos directamente el estado del teclado con un par de simples instrucciones (LD + IN). +El lector debería extraer una conclusión adicional del ejemplo del teclado: la gran diferencia de proceso que hay entre programar en ensamblador y programar en BASIC. Supongamos que nos interesa leer el estado del teclado para saber si unas determinadas teclas están pulsadas o no. Para esto, en ensamblador (aunque esto también podemos hacerlo en BASIC) leemos directamente el estado del teclado con un par de simples instrucciones (''LD'' ''IN''). 
  
- En BASIC, por contra, al usar INKEY$ estamos esperando la ejecución de un código que, además de leer TODAS las filas del teclado (no sólo aquellas de las teclas que nos interesen), realiza una conversión de todos los bits pulsados o no pulsados mediante una tabla para al final proporcionarnos el código ASCII de la tecla pulsada. + En BASIC, por contra, al usar ''INKEY$'' estamos esperando la ejecución de un código que, además de leer TODAS las filas del teclado (no sólo aquellas de las teclas que nos interesen), realiza una conversión de todos los bits pulsados o no pulsados mediante una tabla para al final proporcionarnos el código ASCII de la tecla pulsada. 
  
  Lo que son varias instrucciones simples en ASM, en BASIC se realiza mediante cientos de instrucciones en ensamblador que nos acaban dando la última tecla pulsada. Es por eso que el intérprete BASIC es tan lento: cada operación BASIC son decenas, cientos o miles de instrucciones en ensamblador que ni vemos ni controlamos. Programando directamente en ASM, el microprocesador hará EXCLUSIVAMENTE lo que nosotros le digamos que haga. He aquí la "mágica" diferencia de velocidad entre ambos lenguajes.  Lo que son varias instrucciones simples en ASM, en BASIC se realiza mediante cientos de instrucciones en ensamblador que nos acaban dando la última tecla pulsada. Es por eso que el intérprete BASIC es tan lento: cada operación BASIC son decenas, cientos o miles de instrucciones en ensamblador que ni vemos ni controlamos. Programando directamente en ASM, el microprocesador hará EXCLUSIVAMENTE lo que nosotros le digamos que haga. He aquí la "mágica" diferencia de velocidad entre ambos lenguajes.
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)"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)'' ''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 355: Línea 361:
 Una vez hemos visto todas las partes funcionales, veamos cómo funciona el Spectrum a nivel de ciclo de instrucción y ejecución. Una vez hemos visto todas las partes funcionales, veamos cómo funciona el Spectrum a nivel de ciclo de instrucción y ejecución.
  
- Como ya hemos visto, los diferentes dispositivos externos (teclado, altavoz, cassette, elementos conectados al puerto de expansión, joysticks) se comunican con la CPU por medio de puertos de Entrada/Salida (Puertos E/S o I/O Ports). Para acceder a ellos simplemente leemos o escribimos en el puerto correspondiente mediante las instrucciones IN y OUT del Z80. También existen instrucciones para acceder a la memoria, tanto para lectura como para escritura.+ Como ya hemos visto, los diferentes dispositivos externos (teclado, altavoz, cassette, elementos conectados al puerto de expansión, joysticks) se comunican con la CPU por medio de puertos de Entrada/Salida (Puertos E/S o I/O Ports). Para acceder a ellos simplemente leemos o escribimos en el puerto correspondiente mediante las instrucciones ''IN'' ''OUT'' del Z80. También existen instrucciones para acceder a la memoria, tanto para lectura como para escritura.
  
  En realidad el Z80, visto de una forma simplificada, sólo puede hacer 3 cosas: leer/escribir en la memoria, leer/escribir en los dispositivos de Entrada/Salida y decodificar/ejecutar instrucciones. La parte de lectura/decodificación/ejecución es el funcionamiento principal del microprocesador, y es lo que trataremos a continuación.  En realidad el Z80, visto de una forma simplificada, sólo puede hacer 3 cosas: leer/escribir en la memoria, leer/escribir en los dispositivos de Entrada/Salida y decodificar/ejecutar instrucciones. La parte de lectura/decodificación/ejecución es el funcionamiento principal del microprocesador, y es lo que trataremos a continuación.
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>
  
- En realidad, según el texto "The Undocumented Z80 Documented", de Sean Young, + En realidad, según el texto "The Undocumented Z80 Documented", de Sean Young, tras un reset los registros quedan con los siguientes valores:
-tras un reset los registros quedan con los siguientes valores:+
  
 <code> <code>
  PC              = $0000  PC              = $0000
- AF, SP          = $FFFF+ AF, SP          = $ffff
  Resto           = Valor indefinido  Resto           = Valor indefinido
  Interrupciones  Interrupciones
Línea 430: 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 437: 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 470: 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 503: 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 521: 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, 00se corresponde con la instrucción "3E 00", donde "3E" es el código de operación (opcode) y "00es 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", y "$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'', y **$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 DEes "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 541: 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 554: 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 593: 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.
 +
 +Saber los tiempos de ejecución y la cantidad de bytes que ocupa cada instrucción es vital para escribir rutinas óptimas, y a veces los programadores recurren a trucos para escribir rutinas que ocupen menos espacio, o que se ejecuten en menos ciclos de reloj, o ambas cosas, según las necesidades de cada programa.
 +
 +En sistemas con un procesador tan limitado y con tan poca memoria como el Spectrum cada byte y cada t-estado cuenta. Por ejemplo, la siguiente instrucción:
 +
 +<code z80>
 +  ld a, 0         ; A = 0
 +</code>
 +
 +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:
 +
 +<code z80>
 +  xor a           ; A = A xor a = 0
 +</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.
 +
 +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.
  
 \\  \\ 
 ===== El software de Spectrum ===== ===== El software de Spectrum =====
  
-A estas alturas ya debemos tener claro cómo funciona el Spectrum, con su microprocesador Z80 continuamente ejecutando el código apuntado por "PC", incrementando este y de nuevo repitiendo el ciclo.+A estas alturas ya debemos tener claro cómo funciona el Spectrum, con su microprocesador Z80 continuamente ejecutando el código apuntado por **PC**, incrementando este y de nuevo repitiendo el ciclo.
  
 Cuando encendemos nuestro Spectrum, PC vale 0000h y se ejecuta la ROM que, como ya hemos comentado, no es más que un programa hecho por los ingenieros que desarrollaron el Spectrum. Cuando encendemos nuestro Spectrum, PC vale 0000h y se ejecuta la ROM que, como ya hemos comentado, no es más que un programa hecho por los ingenieros que desarrollaron el Spectrum.
Línea 609: Línea 634:
  
 \\  \\ 
-   - Desde cinta: Nuestro LOAD "" provoca en BASIC la llamada a una rutina de la ROM que carga desde cinta el código y los datos de los programas. Lo único que se hace es leer de la cinta los opcodes y sus operandos, así como cualquier otro dato (gráficos, sonidos) del programa, e introducirlos en memoria en una zona a la que luego saltaremos (cambiaremos PC a ella). Cuando grabamos a cinta, lo que hacemos es leer el contenido de un trozo de memoria y escribirlo en cinta (escribir los valores numéricos de los opcodes, operandos y datos).\\ +   - Desde cinta: Nuestro ''LOAD ""'' provoca en BASIC la llamada a una rutina de la ROM que carga desde cinta el código y los datos de los programas. Lo único que se hace es leer de la cinta los opcodes y sus operandos, así como cualquier otro dato (gráficos, sonidos) del programa, e introducirlos en memoria en una zona a la que luego saltaremos (cambiaremos PC a ella). Cuando grabamos a cinta, lo que hacemos es leer el contenido de un trozo de memoria y escribirlo en cinta (escribir los valores numéricos de los opcodes, operandos y datos).\\ 
    - Desde disco: exactamente igual que en el caso de la cinta, pero el medio de almacenamiento es un disco de 3" o de 3.5".\\     - Desde disco: exactamente igual que en el caso de la cinta, pero el medio de almacenamiento es un disco de 3" o de 3.5".\\ 
    - Ficheros TAP y TZX: son ficheros de ordenador que almacenan los datos exactamente igual que si fuera una cinta real: almacenan opcodes, datos y operandos, que luego serán cargados en memoria.\\     - Ficheros TAP y TZX: son ficheros de ordenador que almacenan los datos exactamente igual que si fuera una cinta real: almacenan opcodes, datos y operandos, que luego serán cargados en memoria.\\ 
Línea 649: Línea 674:
  
 \\  \\ 
-**[ [[.:.:esqueleto_programa|⬅]] | [[.:lenguaje_1|➡]] ]**+**[ [[.:indice|⬉]] | [[.:esqueleto_programa|⬅]] | [[.:lenguaje_1|➡]] ]**
  
  • cursos/ensamblador/arquitectura.1704552136.txt.gz
  • Última modificación: 06-01-2024 14:42
  • por sromero