cursos:ensamblador:lenguaje_1

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:lenguaje_1 [13-01-2024 10:14] sromerocursos:ensamblador:lenguaje_1 [22-01-2024 07:51] (actual) – [Instrucciones LD (instrucciones de carga)] sromero
Línea 3: Línea 3:
 ====== Arquitectura del Z80 e Instrucciones básicas ====== ====== Arquitectura del Z80 e Instrucciones básicas ======
  
-\\  +Empezaremos nuestro periplo por el ensamblador del Z80 viendo en detalle los **registros del procesador**: qué registros hay disponibles, cómo se agrupan, y el registro especial de Flags, enlazando el uso de estos registros con las instrucciones de carga, de operaciones aritméticas, y de manejo de bits, que serán las que trataremos hoy.
- +
- En este capítulo explicaremos la sintaxis utilizada en los programas en ensamblador. Para ello comenzaremos con una definición general de la sintaxis para el ensamblador Pasmo, que será el "traductor" que usaremos entre el lenguaje ensamblador y el código máquina del Z80+
- +
-Posteriormente veremos en detalle los registros: qué registros hay disponibles, cómo se agrupan, y el registro especial de Flags, enlazando el uso de estos registros con las instrucciones de carga, de operaciones aritméticas, y de manejo de bits, que serán las que trataremos hoy. +
- +
-Esta entrega del curso es delicada y complicada: por un lado, tenemos que explicar las normas y sintaxis del ensamblador cruzado Pasmo antes de que conozcamos la sintaxis del lenguaje ensamblador en sí, y por el otro, no podremos utilizar Pasmo hasta que conozcamos la sintaxis del lenguaje. +
- +
-Además, el lenguaje ensamblador tiene disponibles muchas instrucciones diferentes, y nos resultaría imposible explicarlas todas en un mismo capítulo, lo que nos fuerza a explicar las instrucciones del microprocesador en varias entregas. Esto implica que hablaremos de Pasmo comentando reglas, opciones de instrucciones y directivas que todavía no conocemos. +
- +
-Es por esto que recomendamos al lector que, tras releer anteriores capítulos de este libro, se tome esta entrega de una manera especial, leyéndola 2 veces. La "segunda pasada" sobre el texto permitirá enlazar todos los conocimientos dispersos en el mismo, y que no pueden explicarse de una manera lineal porque están totalmente interrelacionados. Además, la parte relativa a la sintaxis de Pasmo será una referencia obligada para posteriores capítulos (mientras continuemos viendo diferentes instrucciones ASM y ejemplos). +
- +
-\\  +
-===== Sintaxis del lenguaje ASM en Pasmo ===== +
- +
-En anteriores capítulos ya hablamos de Pasmo, el ensamblador cruzado que recomendamos para el desarrollo de programas para Spectrum. Este ensamblador traduce nuestros ficheros de texto .asm con el código fuente de programa (en lenguaje ensamblador) a ficheros .bin (o .tap/.tzx) que contendrán el código máquina directamente ejecutable por el Spectrum. +
- +
-Supondremos para el resto del capítulo que ya tenéis instalado Pasmo (ya sea la versión Windows o la de UNIX/Linux) en vuestro sistema y que sabéis utilizarlo de forma básica (bastará con saber realizar un simple ensamblado de programa, como ya vimos en el primer capítulo), y que podéis ejecutarlo dentro del directorio de trabajo que habéis elegido. +
- +
-El ciclo de desarrollo con Pasmo será el siguiente: +
- +
-  * Con un editor de texto, tecleamos nuestro programa en un fichero .ASM con la sintaxis que veremos a continuación. +
-  * Salimos del editor de texto y ensamblamos el programa: +
-    * Si queremos generar un fichero .bin de código objeto cuyo contenido POKEar en memoria (o cargar con ''LOAD "" CODE'') desde un cargador BASIC, lo ensamblamos con: ''pasmo ejemplo1.asm ejemplo1.bin'' +
-    * Si queremos generar un fichero .tap directamente ejecutable (de forma que sea pasmo quien añada el cargador BASIC), lo ensamblamos con ''pasmo <nowiki>--</nowiki>tapbas ejemplo1.asm ejemplo1.tap''+
- +
- Todo esto se mostró bastante detalladamente en su momento en el primer capítulo del curso. +
- +
- Con esto, ya sabemos ensamblar programas creados adecuadamente, de modo que la pregunta es: ¿cómo debo escribir mi programa para que Pasmo pueda ensamblarlo? +
- +
- Es sencillo: escribiremos nuestro programa en un fichero de texto con extensión .asm. En este fichero de texto se ignorarán las líneas en blanco y los comentarios, que en ASM de Z80 se introducen con el símbolo ";" (punto y coma), de forma que todo lo que el ensamblador encuentre a la derecha de un ; será ignorado (siempre que no forme parte de una cadena). Ese fichero de texto será ensamblado por Pasmo y convertido en código binario. +
- +
-Lo que vamos a ver a continuación son las normas que debe cumplir un programa para poder ser ensamblado en Pasmo. Es necesario explicar estas reglas para que el lector pueda consultarlas en el futuro, cuando esté realizando sus propios programas. No te preocupes si no entiendes alguna de las reglas, cuando llegues al momento de implementar tus primeras rutinas, las siguientes normas te serán muy útiles:\\  +
- +
-  * **Normas para las instrucciones:** +
-    * Pondremos una sóla instrucción de ensamblador por línea. +
-    * Como existen diferencias entre los "fin de línea" entre Linux y Windows, es recomendable que los programas se ensamblen con Pasmo en la misma plataforma de S.O. en que se han escrito. Si Pasmo intenta compilar en Windows un programa ASM escrito en un editor de texto de Linux (con retornos de carro de Linux) es posible que obtengamos errores de ensamblado (aunque no es seguro). Si os ocurre al compilar los ejemplos que os proporcionamos (están escritos en Linux) y usáis Windows, lo mejor es abrir el fichero .ASM con notepad y grabarlo de nuevo (lo cual lo salvará con formato de retornos de carro de Windows). El fichero "regrabado" con Notepad podrá ser ensamblado en Windows sin problemas. +
-    * Dado que cada editar renderiza los tabuladores con diferentes tamaños, recomendamos utilizar espacios para indentar. Para eso, utiliza la indentación de "tabulador inteligente" de tu editor, es decir, que el propio editor inserte espacios cuando utilices el tabulador. Es la única forma de que un listado se vea igual en cualquier editor y sistema operativo, algo que no ocurre con los tabuladores reales. +
-    * Además de una instrucción, en una misma línea podremos añadir etiquetas (para referenciar a dicha línea, algo que veremos posteriormente) y comentarios (con ';'). +
-    * Las instrucciones y los registros pueden estar tanto en mayúsculas como en minúsculas. Es igual de válido ''LD A, 10'' que ''ld a, 10''. Usar mayúsculas o minúsculas es una preferencia personal de cada programador, y podemos encontrar listados de las 2 formas. +
-    * De la misma forma, hay quien pone espacios tras las comas y quien no. Es igual de válido ''ld a, 10'' que ''ld a,10''. De nuevo, es una cuestión de preferencia personal. +
-\\  +
-  * **Normas para los valores numéricos:** +
-    * Todos los valores numéricos se considerarán, por defecto, escritos en decimal. +
-    * Para introducir valores números en hexadecimal los precederemos del carácter "$", y para escribir valores numéricos en binario lo haremos mediante el carácter "%"+
-    * Podremos también especificar la base del literal poniendoles como prefijo las cadena &H ó 0x (para hexadecimal) o &O (para octal). +
-    * Podemos especificar también los números mediante sufijos: Usando una "H" para hexadecimal, "D" para decimal, "B" para binario u "O" para octal (tanto mayúsculas como minúsculas). +
-\\  +
-  * **Normas para cadenas de texto: ** +
-    * Podemos separar las cadenas de texto mediante comillas simples o dobles. +
-    * El texto encerrado entre comillas simples no recibe ninguna interpretación, excepto si se encuentran 2 comillas simples consecutivas, que sirven para introducir una comilla simple en la cadena. +
-    * El texto encerrado entre comillas dobles permite introducir caracteres especiales al estilo de C/C++ como \n, \r o \t (nueva línea, retorno de carro, tabulador...). +
-    * El texto encerrado entre comillas dobles también admite \xNN para introducir el carácter correspondiente a un número hexadecimal NN. +
-    * Una cadena de texto de longitud 1 (un carácter) puede usarse como una constante (valor ASCII del carácter) en expresiones como, por ejemplo, 'C'+10h. +
-\\  +
-  * **Normas para los nombres de ficheros:** +
-    * Si vemos que nuestro programa se hace muy largo y por lo tanto incómodo para editarlo, podemos partir el fichero en varios ficheros e incluirlos mediante directivas INCLUDE (para incluir ficheros ASM) o INCBIN (para incluir código máquina ya compilado). Al especificar nombres de ficheros, deberán estar entre dobles comillas o simples comillas. +
-\\  +
-  * **Normas para los identificadores:** +
-    * Los identificadores son los nombres usados para etiquetas y también los símbolos definidos mediante EQU y DEFL. +
-    * Podemos utilizar cualquier cadena de texto, excepto los nombres de las palabras reservadas de ensamblador. +
-\\  +
-  * **Normas para las etiquetas:** +
-    * Una etiqueta es un identificador de texto que ponemos poner al principio de cualquier línea de nuestro programa, por ejemplo: "bucle:" +
-    * Podemos añadir el tradicional sufijo ":" a las etiquetas, pero también es posible no incluirlo si queremos compatibilidad con otros ensambladores que no lo soporten (por si queremos ensamblar nuestro programa con otro ensamblador que no sea pasmo). +
-    * Para un programa ensamblador, cualquier referencia a una etiqueta a lo largo del programa se convierte en una referencia a la posición de memoria de la instrucción o dato siguiente a donde hemos colocado la etiqueta. Podemos utilizar así etiquetas para hacer referencia a nuestros gráficos, variables, datos, funciones, lugares a donde saltar, etc. +
-    * Debido a esto, no podemos usar 2 etiquetas iguales en el programa. Es decir, dentro de 2 rutinas diferentes donde hacemos algún bucle, no podemos usar una etiqueta de nombre por ejemplo "bucle:". Los nombres de etiquetas tienen que ser únicos. No obstante, algunos programas ensambladores como pasmo o sjasmplus permiten una cosa que se llama "etiquetas locales" en las que el nombre de la etiqueta sólo existe dentro de la rutina que la contiene, por lo que podremos tener 2 etiquetas con el mismo nombre para ese tipo de casuística que poníamos como ejemplo. +
-\\  +
-  * **Directivas:** +
-    * Tenemos a nuestra disposición una serie de directivas para facilitarnos la programación, como ''DEFB'' o ''DB'' para introducir datos en crudo en nuestro programa, ''ORG'' para indicar una dirección de inicio de ensamblado, ''END'' para finalizar el programa e indicar una dirección de autoejecución, ''IF/ELSE/ENDIF'' en tiempo de compilación, ''INCLUDE'' e ''INCBIN'', ''MACRO'' y ''REPT/ENDR''+
-    * Una de las directivas más importantes es ''ORG'', que indica la posición origen donde almacenar el código que la sigue. Podemos utilizar diferentes directivas ORG en un mismo programa. Los datos o el código que siguen a una directiva ''ORG'' son ensamblados a partir de la dirección que indica éste. Esta directiva, como el resto de directivas del ensamblador (''END'', ''INCLUDE'', etc) tiene que ir indentada en el código 4 espacios (no debe aparecer al principio de la línea) para que el código sea compatible con otros assemblers (no es algo obligatorio para sjasmplus). +
-    * La directiva ''END'' permite indicar un parámetro numérico (''END XXXX'') que ''pasmo <nowiki>--</nowiki>tapbas'' toma para añadir al listado BASIC de arranque el ''RANDOMIZE USR XXXX'' correspondiente. De esta forma, podemos hacer que nuestros programas arranquen en su posición correcta sin que el usuario tenga que teclear el ''RANDOMIZE USR DIRECCION_INICIO''+
-    * Iremos viendo el significado de las directivas conforme las vayamos usando, pero es aconsejable [[http://www.arrakis.es/~ninsesabe/pasmo/pasmodoc.html#directives|consultar el manual de Pasmo]] para conocer más sobre ellas. +
-\\  +
-  * **Operadores** +
-    * Podemos utilizar los operadores típicos +, -, *. /, así como otros operadores de desplazamiento de bits como >> y <<. +
-    * Tenemos disponibles operadores de comparación como EQ, NE, LT, LE, GT, GE o los clásicos =, !=, <, >, <=, >=. +
-    * Existen también operadores lógicos como AND, OR, NOT, o sus variantes &, |, !. +
-    * Los operadores sólo tienen aplicación en tiempo de ensamblado, es decir, no podemos multiplicar o dividir en tiempo real en nuestro programa usando * o /. Estos operadores están pensados para que podamos poner expresiones como 32*10+12, en lugar del valor numérico del resultado, por ejemplo. +
-    * A la hora de utilizar el paréntesis para poner un cálculo inmediato en una expresión, como por ejemplo ((32*10)+12), tenemos que tener cuidado ya que paréntesis rodeando la expresión pueden ser interpretados por el ensamblador como el operador de acceso a memoria, y no como paréntesis matemáticos. +
- +
-\\  +
-===== Aspecto de un programa en ensamblador ===== +
- +
-Veamos un ejemplo de programa en ensamblador que muestra el uso de algunas de estas normas, para que las podamos entender fácilmente mediante los comentarios incluidos: +
- +
-<code z80> +
-; Programa de ejemplo para mostrar el aspecto de +
-; un programa típico en ensamblador para Pasmo. +
-; Copia una serie de bytes a la videomemoria con +
-; instrucciones simples (sin optimizar). +
-    ORG 40000 +
- +
-    ; Aqui empieza nuestro programa que copia los +
-    ; 7 bytes desde la etiqueta "datos" hasta la +
-    ; videomemoria ([16384] en adelante). +
- +
-    LD HL, destino     ; HL = destino (VRAM) +
-    LD DE, datos       ; DE = origen de los datos +
-    LD B, 6            ; numero de datos a copiar +
- +
-bucle:               ; etiqueta que usaremos luego +
- +
-    LD A, (DE)         ; Leemos un dato de [DE] +
-    ADD A, valor       ; Le sumamos 1 al dato leído +
-    LD (HL), A         ; Lo grabamos en el destino [HL] +
-    INC DE             ; Apuntamos al siguiente dato +
-    INC HL             ; Apuntamos al siguiente destino +
- +
-    DJNZ bucle         ; Equivale a: +
-                      ; B = B-1 +
-                      ; if (B>0) goto Bucle +
-    RET +
- +
-valor     EQU +
-destino   EQU  18384 +
- +
-datos     DEFB 127, %10101010, 0, 128, $FE, %10000000, 00h +
- +
-    END +
-</code> +
- +
-Algunos detalles a tener en cuenta: +
- +
-  * Se utiliza normalmente una instrucción por línea. Se puede poner más de una separándolas normalmente por ':' pero no se recomienda por temas de legibilidad del código. +
- +
-  * Los comentarios pueden ir en sus propias líneas, o la derecha de las propias instrucciones. +
- +
-  * Podemos definir "constantes" con ''EQU'' para hacer referencia a ellas luego en el código. Son constantes, no variables, es decir, se definen en tiempo de ensamblado y no se cambian con la ejecución del programa. Su uso está pensado para poder escribir código más legible y que podamos cambiar los valores asociados posteriormente de una forma sencilla (es más fácil cambiar el valor asignado en el ''EQU'', en un único punto, que cambiar un valor en todas sus apariciones en el código). +
- +
-  * Podemos poner etiquetas (como ''bucle'' y ''datos'' -con o sin dos puntos, son ignorados-) para referenciar a una posición de memoria. Así, la etiqueta ''bucle'' del programa anterior hace referencia a la posición de memoria donde se ensamblaría la siguiente instrucción que aparece tras ella. Las etiquetas se usan para poder saltar a ellas (en los bucles y condiciones) mediante un nombre en lugar de tener que calcular nosotros la dirección del salto a mano y poner direcciones de memoria. Es más fácil de entender y programar un ''JP bucle'' que un ''JP $4008'', por ejemplo. En el caso de la etiqueta ''datos'', nos permite referenciar la posición en la que empiezan los datos que vamos a copiar. +
- +
-  * Podemos ubicar en cualquier posición de memoria (antes o después de las rutinas) datos (que no instrucciones) para utilizarlas como hacemos en el ejemplo anterior con la directiva ''DEFB'' (o su abreviatura ''DB'') insertando después del ''RET'' una serie de bytes que estarán en memoria justo después (en nuestro ejemplo) donde esté la instrucción ''RET'' ensamblada. Y además, le podemos poner una etiqueta delante (como ''datos'') para poder hacer referencia desde el código a la posición exacta de memoria donde está el primer byte de la ristra de datos definidos con ''DEFB''+
- +
-  * Los datos definidos con ''DEFB'' pueden estar en cualquier formato numérico, como se ha mostrado en el ejemplo: decimal, binario, hexadecimal tanto con prefijo "$" como con sufijo "h", etc. +
- +
-Podéis ensamblar el ejemplo anterior con pasmo mediante: +
- +
-<code> +
-pasmo --tapbas ejemplo.asm ejemplo.tap +
-</code> +
- +
-Una vez cargado y ejecutado el TAP en el emulador de Spectrum, podréis ejecutar el código máquina en BASIC con un ''RANDOMIZE USR 40000'', y deberéis ver una pantalla como la siguiente: +
- +
-\\  +
-{{ cursos:ensamblador:ejemplo1.gif?638 |Salida del ejemplo 1}} +
-\\ +
  
-Los píxeles que aparecen en el centro de la pantalla (dirección de memoria 18384) se corresponden con los valores numéricos que hemos definido en "datos"ya que los hemos copiado desde "datos" hasta la videomemoria. No os preocupéis por ahora si no entendéis alguna de las instrucciones utilizadas, las iremos viendo poco a poco y al final tendremos una visión global y concreta de todas ellas.+El lenguaje ensamblador tiene disponibles muchas instrucciones diferentes, y resultaría imposible explicarlas todas en un mismo capítulolo que nos fuerza a explicar las instrucciones del microprocesador en varias entregas.
  
- Si cambiáis el ''END'' al final del programa por ''END 40000''no tendréis la necesidad de ejecutar ''RANDOMIZE USR 40000'' ya que pasmo lo introducirá en el listado BASIC de "arranque" que genera de forma automática con el flag ''--tapbas''.+Comencemos con los conceptos más importantes y las instrucciones más básicascomo las instrucciones de carga, incremento, decremento, operaciones matemáticas básicas e instrucciones de intercambio.
  
 \\  \\ 
 ===== Los registros ===== ===== Los registros =====
  
-Como ya vimos en la anterior entrega, todo el "trabajo de campo" lo haremos con //los registros de la CPU//, que no son más que //variables de 8 y 16 bits integradas dentro del Z80// y que por tanto son muy rápidos para realizar operaciones con ellos.+Como ya vimos en el capítulo dedicado a la Arquitectura del Spectrum y del Z80, todo el "trabajo de campo" lo haremos con //los registros de la CPU//, que no son más que //variables de 8 y 16 bits integradas dentro del Z80// y que por tanto son muy rápidos para realizar operaciones con ellos.
  
 El Z80 tiene una serie de registros de 8 bits con nombres específicos: El Z80 tiene una serie de registros de 8 bits con nombres específicos:
Línea 169: Línea 23:
 Además, podemos agrupar algunos de estos registros en pares de 16 bits para determinadas operaciones: Además, podemos agrupar algunos de estos registros en pares de 16 bits para determinadas operaciones:
 \\  \\ 
-    * **AF**: Formado por el registro A como byte más significativo (Byte alto) y por F como byte menos significativo (Byte bajo). Si A vale $FF y F vale $00, AF valdrá automáticamente "$FF00".+    * **AF**: Formado por el registro A como byte más significativo (Byte alto) y por F como byte menos significativo (Byte bajo). Si ''A'' vale **$ff** ''F'' vale **$00**''AF'' valdrá automáticamente **$ff00**.
     * **BC**: Agrupación de los registros B y C que se puede utilizar en bucles y para acceder a puertos. También se utiliza como "repetidor" o "contador" en las operaciones de acceso a memoria (''LDIR'', ''LDDR'', etc.).     * **BC**: Agrupación de los registros B y C que se puede utilizar en bucles y para acceder a puertos. También se utiliza como "repetidor" o "contador" en las operaciones de acceso a memoria (''LDIR'', ''LDDR'', etc.).
     * **DE, HL**: Registros de 16 bits formados por D y E por un lado y H y L por otro. Utilizaremos generalmente estos registros para leer y escribir en memoria en una operación única, así como para las operaciones de acceso a memoria como ''LDIR'', ''LDDR'', etc.     * **DE, HL**: Registros de 16 bits formados por D y E por un lado y H y L por otro. Utilizaremos generalmente estos registros para leer y escribir en memoria en una operación única, así como para las operaciones de acceso a memoria como ''LDIR'', ''LDDR'', etc.
Línea 181: Línea 35:
  Por último, tenemos disponible un banco alternativo de registros, conocidos como **Shadow Registers** o //Registros Alternativos//, que se llaman igual que sus equivalentes principales pero con una comilla simple detrás: A', F', B', C', D'. E', H' y L'.  Por último, tenemos disponible un banco alternativo de registros, conocidos como **Shadow Registers** o //Registros Alternativos//, que se llaman igual que sus equivalentes principales pero con una comilla simple detrás: A', F', B', C', D'. E', H' y L'.
  
-En cualquier momento podemos intercambiar el valor de los registros A, B, C, D, E, F, H y L con el valor de los registros A', B', C', D', E', F', H' y L' mediante las instrucciones de ensamblador ''EX AF, AF<nowiki>'</nowiki>'' y ''EXX''. La utilidad de estos Shadow Registers es almacenar valores temporales y proporcionarnos más registros para operar: podremos intercambiar el valor de los registros actuales con los temporales, realizar operaciones con los registros sin perder los valores originales (que al hacer el intercambio se quedarán en los registros Shadow), y después recuperar los valores originales volviendo a ejecutar un intercambio.+En cualquier momento podemos intercambiar el valor de los registros A, B, C, D, E, F, H y L con el valor de los registros A', B', C', D', E', F', H' y L' mediante las instrucciones de ensamblador ''EX AF, AF<nowiki>'</nowiki>'' y ''exx''. La utilidad de estos Shadow Registers es almacenar valores temporales y proporcionarnos más registros para operar: podremos intercambiar el valor de los registros actuales con los temporales, realizar operaciones con los registros sin perder los valores originales (que al hacer el intercambio se quedarán en los registros Shadow), y después recuperar los valores originales volviendo a ejecutar un intercambio.
  
 Ya conocemos los registros disponibles, veamos ahora ejemplos de operaciones típicas que podemos realizar con ellos: Ya conocemos los registros disponibles, veamos ahora ejemplos de operaciones típicas que podemos realizar con ellos:
Línea 193: Línea 47:
  
 <code z80> <code z80>
-    LD C, $00       ; C vale 0 +    ld c, $00            ; C vale 0 
-    LD B, $01       ; B vale 1 +    ld b, $01            ; B vale 1 
-                    ; con esto, BC = $0100 +                         ; con esto, BC = $0100 
-    LD AB         ; A ahora vale 1 +    ld ab              ; A ahora vale 1 
-    LD HL, $1234    ; HL vale $1234 o 4660d +    ld hl, $1234         ; HL vale $1234 o 4660d 
-    LD A, (HL     ; A contiene el valor de (4660) +    ld a, (hl          ; A contiene el valor de (4660) 
-    LD A, (16384)   ; A contiene el valor de (16384) +    ld a, (16384)        ; A contiene el valor de (16384) 
-    LD (16385), A   ; Escribimos en (16385) el valor de A +    ld (16385), a        ; Escribimos en (16385) el valor de A 
-    ADD AB        ; Suma: A = A + B +    add ab             ; Suma: A = A + B 
-    INC B           ; Incrementamos B (B = 1+1 =2) +    inc b                ; Incrementamos B (B = 1+1 =2) 
-                    ; Ahora BC vale $0200 +                         ; Ahora BC vale $0200 
-    INC  BC         ; Incrementamos BC +    inc  bc              ; Incrementamos BC 
-                    ; (BC = $0200+1 = $0201)+                         ; (BC = $0200+1 = $0201)
 </code> </code>
  
- Dentro del ejemplo anterior queremos destacar el operador "()", que significa "el contenido de la memoria apuntado por". Así, ''LD A, (16384)'' no quiere decir "mete en A el valor 16384" (cosa que además no se puede hacer porque A es un registro de 8 bits), sino "mete en A el valor de 8 bits que contiene la celdilla de memoria 16384" (equivalente a utilizar en BASIC las funciones ''PEEK'' y ''POKE'', como en ''LET A=PEEK 16384'').+ Dentro del ejemplo anterior queremos destacar el operador "()", que significa "el contenido de la memoria apuntado por". Así, ''ld a, (16384)'' no quiere decir "mete en A el valor 16384" (cosa que además no se puede hacer porque A es un registro de 8 bits), sino "mete en A el valor de 8 bits que contiene la celdilla de memoria 16384" (equivalente a utilizar en BASIC las funciones ''PEEK'' y ''POKE'', como en ''LET A=PEEK 16384'').
  
 Cabe destacar un gran inconveniente del juego de instrucciones del Z80, y es que no es //ortogonal//. Se dice que el juego de instrucciones de un microprocesador es ortogonal cuando puedes realizar todas las operaciones sobre todos los registros, sin presentar excepciones. En el caso del Z80 no es así, ya que hay determinadas operaciones que podremos realizar sobre unos registros pero no sobre otros. Cabe destacar un gran inconveniente del juego de instrucciones del Z80, y es que no es //ortogonal//. Se dice que el juego de instrucciones de un microprocesador es ortogonal cuando puedes realizar todas las operaciones sobre todos los registros, sin presentar excepciones. En el caso del Z80 no es así, ya que hay determinadas operaciones que podremos realizar sobre unos registros pero no sobre otros.
Línea 215: Línea 69:
  
 <code z80> <code z80>
-    LD BC, $1234 +    ld bc, $1234 
-    LD HLBC +    ld hlbc 
-    LD SPBC +    ld spbc 
-    EX DEHL +    ex dehl 
-    EX BCDE +    ex bcde 
-    ADD HLBC +    add hlbc 
-    ADD DEBC+    add debc
 </code> </code>
  
Línea 227: Línea 81:
  
 <code z80> <code z80>
-   LD SPBC      ; NO: No se puede cargar el valor un registro en SP,+   ld spbc      ; NO: No se puede cargar el valor un registro en SP,
                   ; sólo se puede cargar un valor inmediato NN                   ; sólo se puede cargar un valor inmediato NN
  
-   EX BCDE      ; NO: Existe EX DEHL, pero no EX BC, DE+   ex bcde      ; NO: Existe ex dehl, pero no EX BC, DE
  
-   ADD DEBC     ; NO: Sólo se puede usar HL como operando destino+   add debc     ; NO: Sólo se puede usar HL como operando destino
                   ; en las sumas de 16 bytes con registros de propósito                   ; en las sumas de 16 bytes con registros de propósito
                   ; general. Una alternativa sería:                   ; general. Una alternativa sería:
                   ;                   ;
-                  ; LD HL, 0        ; HL = 0 +                  ; ld hl, 0        ; HL = 0 
-                  ; ADD HLBC      ; HL = HL + BC +                  ; add hlbc      ; HL = HL + BC 
-                  ; EX DEHL       ; Intercambiamos el valor de HL y DE+                  ; ex dehl       ; Intercambiamos el valor de HL y DE
  
-   LD BCDE      ; NO:, pero se pueden tomar alternativas, como por ej:+   ld bcde      ; NO:, pero se pueden tomar alternativas, como por ej:
                   ;                   ;
-                  ; PUSH DE +                  ; push de 
-                  ; POP BC+                  ; pop bc
                   :                   :
                   ; o también:                   ; o también:
                   ;                   ;
-                  ; LD BD +                  ; ld bd 
-                  ; LD CE+                  ; ld ce
  
-   LD DEHL      ; NO: mismo caso anterior.+   ld dehl      ; NO: mismo caso anterior.
  
-   LD SPBC      ; NO: no existe como instrucción.+   ld spbc      ; NO: no existe como instrucción.
 </code> </code>
  
Línea 270: Línea 124:
 Debido a esto, no debemos modificar el registro IY, a menos que cumplamos estas 2 condiciones: Debido a esto, no debemos modificar el registro IY, a menos que cumplamos estas 2 condiciones:
  
-1.- Cambiamos el modo de funcionamiento del Spectrum de IM1 (su modo por defecto) a IM2 (un modo "personalizado"). +  - Cambiamos el modo de funcionamiento del Spectrum de im1 (su modo por defecto) a im2 (un modo "personalizado"). 
-2.- No llamamos ni utilizamos ninguna rutina de la ROM para ninguna tarea.+  - No llamamos ni utilizamos ninguna rutina de la ROM para ninguna tarea.
  
 Ambas cosas son el procedimiento habitual en un juego o programa, por lo que podremos utilizar el registro IY en nuestros programas, pero no inicialmente. A lo largo del curso no lo usaremos ya que vamos a utilizar rutinas de la ROM de apoyo para la mayoría de nuestros ejemplos. Ambas cosas son el procedimiento habitual en un juego o programa, por lo que podremos utilizar el registro IY en nuestros programas, pero no inicialmente. A lo largo del curso no lo usaremos ya que vamos a utilizar rutinas de la ROM de apoyo para la mayoría de nuestros ejemplos.
Línea 298: Línea 152:
 <code z80> <code z80>
             ; Repetir algo 100 veces             ; Repetir algo 100 veces
-            LD B, 100+            ld b, 100
     bucle:     bucle:
-            (...)        ; código+            (...)              ; código
  
-            DEC B        ; Decrementamos B (B=B-1) +            dec b              ; Decrementamos B (B=B-1) 
-            JR NZ, bucle+            jr nz, bucle 
 +            
             ; Si el resultado de la operación anterior no es cero (NZ = Non Zero),             ; Si el resultado de la operación anterior no es cero (NZ = Non Zero),
-            ; saltar a la etiqueta bucle y continuar. DEC B hará que el flag Z +            ; saltar a la etiqueta bucle y continuar. dec b hará que el flag Z 
-            ; se ponga a 1 cuando B llegue a cero, lo que afectará al JR NZ.+            ; se ponga a 1 cuando B llegue a cero, lo que afectará al jr NZ.
             ; Como resultado, este trozo de código (...) se ejecutará 100 veces.             ; Como resultado, este trozo de código (...) se ejecutará 100 veces.
  
 </code> </code>
  
- Como veremos en su momento, existe una instrucción equivalente a ''DEC B'' + ''JR NZ'' que es más cómoda de utilizar y más rápida que estas 2 instrucciones juntas (''DJNZ''), pero se ha elegido el ejemplo que tenéis arriba para que veáis cómo muchas operaciones (en este caso DEC) afectan a los flags, y la utilidad que estos tienen a la hora de programar.+ Como veremos en su momento, existe una instrucción equivalente a ''dec b'' + ''JR NZ'' que es más cómoda de utilizar y más rápida que estas 2 instrucciones juntas (''DJNZ''), pero se ha elegido el ejemplo que tenéis arriba para que veáis cómo muchas operaciones (en este caso DEC) afectan a los flags, y la utilidad que estos tienen a la hora de programar.
  
 Además de para bucles, también podemos utilizarlo para comparaciones. Supongamos que queremos hacer en ensamblador una comparación de igualdad, algo como: Además de para bucles, también podemos utilizarlo para comparaciones. Supongamos que queremos hacer en ensamblador una comparación de igualdad, algo como:
Línea 323: Línea 178:
  
 <code z80> <code z80>
-     LD AC              ; A = C+     ld ac                   ; A = C
      ; Tenemos que hacer esto porque no existe      ; Tenemos que hacer esto porque no existe
-     ; una instruccion SUB B. Sólo se puede+     ; una instruccion sub b. Sólo se puede
      ; restar un registro al registro A.      ; restar un registro al registro A.
  
-     SUB B                ; A = A-B +     sub b                     ; A = A-B 
-     JP Z, Es_Igual       ; Si A=B la resta es cero y Z=1 +     jp z, Es_Igual            ; Si A=B la resta es cero y Z=1 
-     JP NZ, No_Es_Igual   ; Si A<>B la resta no es cero y Z=0+     jp nz, No_Es_Igual        ; Si A<>B la resta no es cero y Z=0
      (...)      (...)
  
Línea 370: Línea 225:
  
 <code z80> <code z80>
-LD DESTINO, ORIGEN+ld DESTINO, ORIGEN
 </code> </code>
  
Línea 378: Línea 233:
  
 <code z80> <code z80>
-LD A, 10         ; A = 10 +ld a, 10              ; A = 10 
-LD B, 200        ; B = 200 +ld b, 200             ; B = 200 
-LD BC, 12345     ; BC = 12345+ld bc, 12345          ; BC = 12345
 </code> </code>
  
Línea 386: Línea 241:
  
 <code z80> <code z80>
-LD AB          ; A = B +ld ab               ; A = B 
-LD BCDE        ; BC = DE+ld bcde             ; BC = DE
 </code> </code>
  
Línea 393: Línea 248:
  
 <code z80> <code z80>
-LD (12345), A    ; Memoria[12345] = valor en A +ld (12345), a         ; Memoria[12345] = valor en A 
-LD (HL), 10      ; Memoria[valor de HL] = 10+ld (hl), 10           ; Memoria[valor de HL] = 10
 </code> </code>
  
Línea 400: Línea 255:
  
 <code z80> <code z80>
-LD A, (12345)    ; A = valor en Memoria[12345] +ld a, (12345)         ; A = valor en Memoria[12345] 
-LD B, (HL      ; B = valor en Memoria[valor de HL]+ld b, (hl           ; B = valor en Memoria[valor de HL]
 </code> </code>
  
- Nótese cómo el operador () nos permite acceder a memoria. En nuestros ejemplos, ''LD A, (12345)'' no significa meter en A el valor 12345 (cosa imposible al ser un registro de 16 bits) sino almacenar en el registro A el valor que hay almacenado en la celdilla número 12345 de la memoria del Spectrum.+ Nótese cómo **el operador ''()'' nos permite acceder a la memoria del Spectrum**. En nuestros ejemplos, ''ld a, (12345)'' no significa meter en A el valor 12345 (cosa imposible al ser un registro de 16 bits) sino almacenar en el registro A el valor que hay almacenado en la celdilla número 12345 de la memoria del Spectrum. 
 + 
 + Este operador indica que se hace referencia a una posición de memoria referenciada por el valor que hay dentro de los paréntesis. Dicho valor referencia a una celdilla de memoria de 8 bits. 
 + 
 + Es decir, si escribiéramos en BASIC del Spectrum (con ''PEEK'' y ''POKE'') las instrucciones de carga de 8 bits que referencian a la memoria, veríamos lo siguiente: 
 + 
 +<code z80> 
 +ld a, (16384)     =>    LET A = PEEK 16384 
 +ld (16384), a     =>    POKE 16384, a 
 + 
 +ld hl, 16384      =>    HL = 16384 
 +ld a, (hl)        =>    LET A = PEEK HL  =>    LET A = PEEK 16384 
 +ld (hl), a        =>    POKE HL, a       =>    POKE (16384), a 
 +</code> 
 + 
 +En el segundo ejemplo hemos utilizado ''ld hl, 16384'', que significa "carga en HL el valor 16384". Como no hay paréntesis en la instrucción, no estamos haciendo una referencia a memoria sino al valor inmediato 16384, el cual metemos en HL. Después, al utilizar los paréntesis en ''ld a, (hl)'', sí que hacemos una referencia a memoria, con la dirección absoluta contenida en HL. 
 + 
 +No sólo podemos leer de o escribir en una dirección de memoria valores de 8 bits, también podemos leer y escribir valores de 16 bits. Evidentemente, como la memoria es un conjunto de "celdillas de 8 bits", para leer o escribir valores de 16 bits lo haremos en 2 celdillas: la celdilla apuntada por la dirección, y la siguiente. 
 + 
 +De nuevo, viéndolo "en instrucciones BASIC", podemos ver la diferencia entre asignar un valor de 16 bits inmediato, o referenciar a una posición de memoria para leer 16 bits: 
 + 
 +<code z80> 
 +ld hl, 16384      =>    HL = 16384 
 + 
 +ld hl, (16384)    =>    HL = (PEEK 16384) + 256*(PEEK 16385) 
 +                           => L = PEEK 16384 
 +                              H = PEEK 16385 
 +</code> 
 + 
 +En ''ld hl, (16384)'', metemos en HL el dato de 16 bits en 16384 y 16385, un valor de 8 bits para cada uno de los 2 registrow de 8 bits de HL (concretamente, H será el valor contenido en 16385 y L el valor en 16384, posteriormente veremos por qué se leen en orden inverso). 
 + 
 +De la misma forma, si hablamos de escribir en memoria un valor de 16 bits: 
 + 
 +<code z80> 
 +ld (16384), hl    =>    POKE 16384, L 
 +                        POKE 16385, H 
 +</code>
  
 En un microprocesador con un juego de instrucciones ortogonal, se podría usar cualquier origen y cualquier destino sin distinción. En el caso del Z80 no es así. El listado completo de operaciones válidas con LD es el siguiente: En un microprocesador con un juego de instrucciones ortogonal, se podría usar cualquier origen y cualquier destino sin distinción. En el caso del Z80 no es así. El listado completo de operaciones válidas con LD es el siguiente:
Línea 423: Línea 314:
 <code z80> <code z80>
 ; Carga de valores en registros ; Carga de valores en registros
-LD r, N +ld r, N 
-LD rr, NN +ld rr, NN 
-LD ri, NN+ld ri, NN
  
 ; Copia de un registro a otro ; Copia de un registro a otro
-LD r, r +ld r, r 
-LD rr, rr+ld rr, rr
  
 ; Acceso a memoria ; Acceso a memoria
-LD r, (HL+ld r, (hl
-LD (NN), A +ld (NN), a 
-LD (HL), N +ld (hl), N 
-LD A, (rr)      ; (excepto rr=SP) +ld a, (rr)      ; (excepto rr=SP) 
-LD (rr),      ; (excepto rr=SP) +ld (rr),      ; (excepto rr=SP) 
-LD A, (NN) +ld a, (NN) 
-LD rr, (NN) +ld rr, (NN) 
-LD ri, (NN) +ld ri, (NN) 
-LD (NN), rr +ld (NN), rr 
-LD (NN), ri+ld (NN), ri
  
 ; Acceso indexado a memoria ; Acceso indexado a memoria
-LD (ri+N), r +ld (ri+N), r 
-LD r, (ri+N) +ld r, (ri+N) 
-LD (ri+N), N+ld (ri+N), N
 </code> </code>
  
Línea 453: Línea 344:
 <code z80> <code z80>
 ; Manipulación del puntero de pila (SP) ; Manipulación del puntero de pila (SP)
-LD SP, ri +ld sp, ri 
-LD SPHL+ld sphl
  
 ; Para manipular el registro I ; Para manipular el registro I
-LD AI +ld ai 
-LD IA+ld ia
  
 ; Para manipular el registro R ; Para manipular el registro R
-LD AR +ld ar 
-LD RA+ld ra
 </code> </code>
  
Línea 470: Línea 361:
 ; Carga de valores en registros ; Carga de valores en registros
 ; registro_destino = valor ; registro_destino = valor
-LD A, 100          LD r, N +ld a, 100               ld r, N 
-LD BC, 12345       LD rr, NN+ld bc, 12345            ld rr, NN
  
 ; Copia de registros en registros ; Copia de registros en registros
 ; registro_destino = registro_origen ; registro_destino = registro_origen
-LD BC            LD r, r +ld bc                 ld r, r 
-LD AB            LD r, r +ld ab                 ld r, r 
-LD BCDE          LD rr, rr+ld bcde               ld rr, rr
  
 ; Acceso a memoria ; Acceso a memoria
 ; (Posicion_memoria) = VALOR o bien ; (Posicion_memoria) = VALOR o bien
 ;  Registro = VALOR en (Posicion de memoria) ;  Registro = VALOR en (Posicion de memoria)
-LD A, (HL        LD r, (rr) +ld a, (hl             ld r, (rr) 
-LD (BL), B         LD (rr), r +ld (bc), a              ld (rr), r 
-LD (12345), A      LD (NN), A +ld (12345), a           ld (NN), a 
-LD A, (HL        LD r, (rr) +ld a, (hl             ld r, (rr) 
-LD (DE), A         LD (rr), r +ld (de), a              ld (rr), r 
-LD (BC), 1234h     LD (BC), NN +ld (bc), 1234h          ld (bc), NN 
-LD (12345), DE     LD (NN), rr +ld (12345), de          ld (NN), rr 
-LD IX, (12345)     ; LD ri, (NN) +ld ix, (12345)          ; LD ri, (NN) 
-LD (34567), IY     LD (NN), ri+ld (34567), iy          ld (NN), ri
  
 ; Acceso indexado a memoria ; Acceso indexado a memoria
 ; (Posicion_memoria) = VALOR o VALOR = (Posicion_memoria) ; (Posicion_memoria) = VALOR o VALOR = (Posicion_memoria)
 ; Donde la posicion es IX+N o IY+N: ; Donde la posicion es IX+N o IY+N:
-LD (IX+10), A      ; LD (ri+N), r +ld (ix+10), a           ; LD (ri+N), r 
-LD A, (IY+100)     LD r, (ri+N) +ld a, (iy+100)          ld r, (ri+N) 
-LD (IX-30), 100    ; LD (ri+N), N+ld (ix-30), 100         ; LD (ri+N), N
 </code> </code>
  
  Hagamos hincapié de nuevo en el mismo detalle: debido a que el juego de instrucciones del Z80 no es ortogonal, en ocasiones no podemos ejecutar ciertas operaciones que podrían sernos útiles con determinados registros. En ese caso tendremos que buscar una solución mediante los registros y operaciones válidas de que disponemos.  Hagamos hincapié de nuevo en el mismo detalle: debido a que el juego de instrucciones del Z80 no es ortogonal, en ocasiones no podemos ejecutar ciertas operaciones que podrían sernos útiles con determinados registros. En ese caso tendremos que buscar una solución mediante los registros y operaciones válidas de que disponemos.
  
- Un detalle muy importante respecto a las instrucciones de carga: **en el caso de las operaciones LD, el registro F no ve afectado ninguno de sus indicadores o flags en relación al resultado de la ejecución de las mismas** (salvo en el caso de ''LD AI'' y ''LD AR'').+ Un detalle muy importante respecto a las instrucciones de carga: **en el caso de las operaciones LD, el registro F no ve afectado ninguno de sus indicadores o flags en relación al resultado de la ejecución de las mismas** (salvo en el caso de ''ld ai'' y ''ld ar'').
  
 <code> <code>
                          Flags                          Flags
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
- ---------------------------------- + --------------------------------- 
-   LD r, r           |- - - - - -| +   ld r, r           |- - - - - -| 
-   LD r, N           |- - - - - -| +   ld r, N           |- - - - - -| 
-   LD rr, rr         |- - - - - -| +   ld rr, rr         |- - - - - -| 
-   LD (rr),        |- - - - - -| +   ld (rr),        |- - - - - -| 
-   LD (rr),        |- - - - - -| +   ld (rr),        |- - - - - -| 
-   LD ri, (NN)       |- - - - - -| +   ld ri, (NN)       |- - - - - -| 
-   LD (NN), ri       |- - - - - -| +   ld (NN), ri       |- - - - - -| 
-   LD (ri+d), N      |- - - - - -| +   ld (ri+d), N      |- - - - - -| 
-   LD (ri+d), r      |- - - - - -| +   ld (ri+d), r      |- - - - - -| 
-   LD r, (ri+d)      |- - - - - -| +   ld r, (ri+d)      |- - - - - -| 
-   LD A          |* * 0 * 1 0| +   ld a          |* * 0 * 1 0| 
-   LD A          |* * 0 * 1 0|+   ld a          |* * 0 * 1 0|
 </code> </code>
 \\  \\ 
  
-Esto quiere decir, y es muy importante, que una operación como ''LD A, 0'', por ejemplo, no activará el flag de Zero del registro F.+Esto quiere decir, y es muy importante, que una operación como ''ld a, 0'', por ejemplo, no activará el flag de Zero del registro F.
  
-Al respecto de escritura y lectura de valores de 16 bits utilizando instrucciones que trabajan con 8 bits, queremos recordar en este punto que el Z80 es una CPU LITTLE-ENDIAN por lo que los valores de 16 bits aparecerán en memoria "invertidos", es decir, primero el byte menos significativo y en la celdilla siguiente el byte más significativo.+\\  
 +===== Tamaños y ciclos ===== 
 + 
 +Hay otros dos datos que, como la afectación de flags, son muy importantes sobre las diferentes instrucciones que iremos viendo. 
 + 
 +Uno es el tamaño en bytes de cada instrucción, que viene determinado por los opcodes que ocupa. Así, un ''ld a, b'' ocupa un sólo byte (**$78**), ''ld a, $ff'' ocupa 2 bytes (al opcode **$3E** le sigue el operando **$ff**) y ''ld bc, $1234'' ocupa 3 bytes (al opcode **01** le siguen los 2 bytes de $1234). 
 + 
 +Del mismo modo, tenemos el tiempo que tarda en ejecutarse cada una de ellas, lo que se conoce como el número de **ciclos** (o **t-estados** / **t-states**). Este es el tiempo que tarda el procesador Z80 en leer de memoria, decodificar y ejecutar cada instrucción. Cada lectura de byte de memoria requiere en general de 3 t-estados extra, así que no lo es lo mismo una instrucción sencilla de un sólo byte de opcode como ''ld a, b'' que una con 3 bytes como ''ld bc, $1234''
 + 
 +En nuestro ejemplo anterior, el ''ld a, b'' de un sólo byte se ejecuta en 4 ciclos de reloj (3 para leer de memoria el opcode y 1 para ejecutarlo), el ''ld a, $ff'' son 7 ciclos de reloj o t-estados (los 3 de leer de memoria el primer byte, 3 de leer el segundo byte, y uno más para la ejecución) y ''ld bc, $1234'' que ocupa 3 bytes requiere 3+3+3+1 = 10 ciclos de reloj. 
 + 
 +En estos capítulos iniciales del curso no nos deben de preocupar tanto lo que ocupan las instrucciones y cuánto tardan en ejecutarse como el saber qué instrucciones existen, la manera en que operan y cómo se utilizan. No obstante, sí que necesitaremos más adelante tener un buen conocimiento de tamaño y tiempo de ejecución de cada instrucción para desarrollar programas. 
 + 
 +En el último capítulo dedicado a las diferentes instrucciones veremos una tabla donde se detallan todos los tamaños y tiempos de las diferentes instrucciones. 
 + 
 +Un apunte sobre ''ld bc, $1234'': Al respecto de escritura y lectura de valores de 16 bits utilizando instrucciones que trabajan con 8 bits, queremos recordar en este punto que el Z80 es una CPU LITTLE-ENDIAN por lo que los valores de 16 bits aparecerán en memoria "invertidos", es decir, primero el byte menos significativo y en la celdilla siguiente el byte más significativo. Es decir, que el opcode correspondiente a ''ld bc, $1234'' en memoria no es "**$01 $12 $34**" sino "**$01 $34 $12**";
  
 \\  \\ 
Línea 535: Línea 441:
  
 <code z80> <code z80>
-LD A, 0      ; A = 0 +ld a, 0                ; A = 0 
-INC A        ; A = A+1 = 1 +inc a                  ; A = A+1 = 1 
-LD BA      ; B = A = 1 +ld ba                ; B = A = 1 
-INC B        ; B = B+1 = 2 +inc b                  ; B = B+1 = 2 
-INC B        ; B = B+1 = 3 +inc b                  ; B = B+1 = 3 
-LD  BC, 0 +ld  bc, 0 
-INC BC       ; BC = 0001h +inc bc                 ; BC = $0001 
-INC B        ; BC = 0101h (ya que B=B+1 y es la parte alta) +inc b                  ; BC = $0101 (ya que B=B+1 y es la parte alta) 
-DEC A        ; A = A-1 = 0+dec a                  ; A = A-1 = 0
 </code> </code>
  
Línea 549: Línea 455:
  
 <code z80> <code z80>
-INC +inc 
-DEC +dec 
-INC rr +inc rr 
-DEC rr+dec rr
 </code> </code>
  
Línea 558: Línea 464:
  
 <code z80> <code z80>
-INC (HL+inc (hl
-DEC (HL)+dec (hl)
 </code> </code>
  
Línea 565: Línea 471:
  
 <code z80> <code z80>
-INC (IX+N+inc (ix+n
-DEC (IX+N+dec (ix+n
-INC (IY+N+inc (iy+n
-DEC (IY+N)+dec (iy+n)
 </code> </code>
  
Línea 576: Línea 482:
  
 <code z80> <code z80>
-INC A          ; A = A+1 +inc a               ; A = A+1 
-DEC B          ; B = B-1 +dec b               ; B = B-1 
-INC DE         ; DE = DE+1 +inc de              ; DE = DE+1 
-DEC IX         ; IX = IX-1 +dec ix              ; IX = IX-1 
-INC (HL      ; (HL) = (HL)+1 +inc (hl           ; (HL) = (HL)+1 
-INC (IX-5)     ; (IX-5) = (IX-5)+1 +inc (ix-5)          ; (IX-5) = (IX-5)+1 
-DEC (IY+100)   ; (IY+100) = (IY+100)+1+dec (iy+100)        ; (IY+100) = (IY+100)+1
 </code> </code>
  
 Unos apuntes sobre la afectación de los flags ante el uso de INC y DEC: Unos apuntes sobre la afectación de los flags ante el uso de INC y DEC:
  
-    * Si un registro de 8 bits vale 255 ($FF) y lo incrementamos, pasará a valer 0. +    * Si un registro de 8 bits vale 255 ($ff) y lo incrementamos, pasará a valer 0. 
-    * Si un registro de 16 bits vale 65535 ($FFFF) y lo incrementamos, pasará a valer 0. +    * Si un registro de 16 bits vale 65535 ($ffff) y lo incrementamos, pasará a valer 0. 
-    * Si un registro de 8 bits vale 0 y lo decrementamos, pasará a valer 255 ($FF). +    * Si un registro de 8 bits vale 0 y lo decrementamos, pasará a valer 255 ($ff). 
-    * Si un registro de 16 bits vale 0 ($0) y lo decrementamos, pasará a valer 65535 ($FF).+    * Si un registro de 16 bits vale 0 ($0) y lo decrementamos, pasará a valer 65535 ($ff).
     * En estos desbordamientos no se tomará en cuenta para nada el bit de Carry (acarreo) de los flags (registro F), ni tampoco lo afectarán tras ejecutarse.     * En estos desbordamientos no se tomará en cuenta para nada el bit de Carry (acarreo) de los flags (registro F), ni tampoco lo afectarán tras ejecutarse.
-    * Las operaciones INC y DEC sobre registros de 16 bits (BC, DE, HL, IX, IY, SP) no afectan a los flags. Esto implica que no podemos usar como condición de flag zero para un salto el resultado de instrucciones como "DEC BC", por ejemplo.+    * Las operaciones INC y DEC sobre registros de 16 bits (BC, DE, HL, IX, IY, SP) no afectan a los flags. Esto implica que no podemos usar como condición de flag zero para un salto el resultado de instrucciones como "dec bc", por ejemplo.
     * Las operaciones INC y DEC sobre registros de 8 bits y sobre la memoria no afectan al flag de acarreo, pero sí que pueden afectar al flag de Zero (Z), al de Paridad/Overflow (P/V), al de Signo (S) y al de Half-Carry (H).     * Las operaciones INC y DEC sobre registros de 8 bits y sobre la memoria no afectan al flag de acarreo, pero sí que pueden afectar al flag de Zero (Z), al de Paridad/Overflow (P/V), al de Signo (S) y al de Half-Carry (H).
  
Línea 601: Línea 507:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
-   INC r             |* * * V 0 -| +   inc r             |* * * V 0 -| 
-   INC [HL]          |* * * V 0 -| +   inc (hl)          |* * * V 0 -| 
-   INC [ri+N       |* * * V 0 -| +   inc (ri+N       |* * * V 0 -| 
-   INC rr            |- - - - - -| +   inc rr            |- - - - - -| 
-   DEC r             |* * * V 1 -| +   dec r             |* * * V 1 -| 
-   DEC rr            |- - - - - -|+   dec rr            |- - - - - -|
 </code> </code>
  
Línea 640: Línea 546:
  
 <code z80> <code z80>
-ADD DESTINO, ORIGEN+add DESTINO, ORIGEN
 </code> </code>
  
Línea 646: Línea 552:
  
 <code z80> <code z80>
-ADD A, s +add a, s 
-ADD HL, ss +add hl, ss 
-ADD ri, rr+add ri, rr
 </code> </code>
  
Línea 668: Línea 574:
  
 <code z80> <code z80>
-ADD A, s +add a, s 
-ADD AB        ; A = A + B +add ab             ; A = A + B 
-ADD A, 100      ; A = A + 100 +add a, 100           ; A = A + 100 
-ADD A[HL]     ; A = A + [HL] +add a(hl)          ; A = A + (HL) 
-ADD A[IX+10]  ; A = A + [IX+10]+add a(ix+10)       ; A = A + (IX+10)
  
-ADD HL, ss +add hl, ss 
-ADD HLBC      ; HL = HL + BC +add hlbc           ; HL = HL + BC 
-ADD HLSP      ; HL = HL + SP+add hlsp           ; HL = HL + SP
  
-ADD ri, rr +addri, rr 
-ADD IXBC      ; IX = IX + BC +add ixbc           ; IX = IX + BC 
-ADD IYDE      ; IY = IY + DE +add iyde           ; IY = IY + DE 
-ADD IYIX      ; IY = IY + IX +add iyix           ; IY = IY + IX 
-ADD IXIY      ; IX = IX + IY+add ixiy           ; IX = IX + IY
 </code> </code>
  
Línea 688: Línea 594:
  
 <code z80> <code z80>
-ADD BC      ; Sólo A puede ser destino +add bc             ; Sólo A puede ser destino 
-ADD BCDE    ; Sólo puede ser destino HL +add bcde           ; Sólo puede ser destino HL 
-ADD IXIX    ; No podemos sumar un registro índice a él mismo+add ixix           ; No podemos sumar un registro índice a él mismo
 </code> </code>
  
 La afectación de los flags ante las operaciones de sumas es la siguiente: La afectación de los flags ante las operaciones de sumas es la siguiente:
  
-   * Para ''ADD A, s'', el registro N (Substraction) se pone a 0 (lógicamente, ya que sólo se pone a uno cuando se ha realizado una resta). El registro P/V se comporta como un registro de Overflow e indica si ha habido overflow (desbordamiento) en la operación. El resto de flags (Sign, Zero, Half-Carry y Carry) se verán afectados de acuerdo al resultado de la operación de suma.+   * Para ''add a, s'', el registro N (Substraction) se pone a 0 (lógicamente, ya que sólo se pone a uno cuando se ha realizado una resta). El registro P/V se comporta como un registro de Overflow e indica si ha habido overflow (desbordamiento) en la operación. El resto de flags (Sign, Zero, Half-Carry y Carry) se verán afectados de acuerdo al resultado de la operación de suma.
  
-   * Para ''ADD HL, ss'' y ''ADD ri, rr'', se pone a 0 el flag N, y sólo se verá afectado el flag de acarreo (C) de acuerdo al resultado de la operación.+   * Para ''add hl, ss'' y ''add ri, rr'', se pone a 0 el flag N, y sólo se verá afectado el flag de acarreo (C) de acuerdo al resultado de la operación.
  
 O, en forma de tabla de afectación: O, en forma de tabla de afectación:
Línea 705: Línea 611:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- ADD A, s            |* * * V 0 *| + add a, s            |* * * V 0 *| 
- ADD HL, ss          |- - ? - 0 *| + add hl, ss          |- - ? - 0 *| 
- ADD ri, rr          |- - ? - 0 *|+ add ri, rr          |- - ? - 0 *|
 </code> </code>
  
Línea 736: Línea 642:
  
 <code z80> <code z80>
-LD A, %10000000 +ld a, %10000000 
-LD B, %10000000 +ld b, %10000000 
-ADD AB+add ab
 </code> </code>
  
Línea 746: Línea 652:
 ==== Resta: SUB (Substract) ==== ==== Resta: SUB (Substract) ====
  
-En el caso de las restas, sólo es posible realizar (de nuevo gracias a la no ortogonalidad del J.I. del Z80) la operación "A=A-origen", donde "origen" puede ser cualquier registro de 8 bits, valor inmediato de 8 bits, contenido de la memoria apuntada por [HL], o contenido de la memoria apuntada por un registro índice más un desplazamiento. El formato de la instrucción ''SUB'' no requiere 2 operandos, ya que el registro destino sólo puede ser A:+En el caso de las restas, sólo es posible realizar (de nuevo gracias a la no ortogonalidad del J.I. del Z80) la operación "A=A-origen", donde "origen" puede ser cualquier registro de 8 bits, valor inmediato de 8 bits, contenido de la memoria apuntada por (HL), o contenido de la memoria apuntada por un registro índice más un desplazamiento. El formato de la instrucción ''SUB'' no requiere 2 operandos, ya que el registro destino sólo puede ser A:
  
 <code z80> <code z80>
-SUB ORIGEN+sub ORIGEN
 </code> </code>
  
Línea 755: Línea 661:
  
 <code z80> <code z80>
-SUB        ; A = A - r +sub                ; A = A - r 
-SUB        ; A = A - N +sub                ; A = A - N 
-SUB [HL]     ; A = A - [HL] +sub (hl)             ; A = A - (HL) 
-SUB [rr+d]   ; A = A - [rr+d]+sub (rr+d)           ; A = A - (rr+d)
 </code> </code>
  
Línea 764: Línea 670:
  
 <code z80> <code z80>
-SUB B           ; A = A - B +sub b           ; A = A - B 
-SUB 100         ; A = A - 100 +sub 100         ; A = A - 100 
-SUB [HL]        ; A = A - [HL] +sub (hl)        ; A = A - (HL) 
-SUB [IX+10    ; A = A - [IX+10]+sub (ix+10    ; A = A - (IX+10)
 </code> </code>
  
Línea 788: Línea 694:
  
 <code> <code>
-"ADC A, s"    equivale a    "A = A + s + CarryFlag" +"adc a, s"    equivale a    "A = A + s + CarryFlag" 
-"ADC HL, ss"  equivale a    "HL = HL + ss + CarryFlag"+"adc hl, ss"  equivale a    "HL = HL + ss + CarryFlag"
 </code> </code>
  
Línea 800: Línea 706:
   Instrucción       |S Z H P N C|   Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- ADC A,s            |* * * V 0 *| + adc a,s            |* * * V 0 *| 
- ADC HL,ss          |* * ? V 0 *|+ adc hl,ss          |* * ? V 0 *|
 </code> </code>
  
Línea 813: Línea 719:
  
 <code> <code>
-"SBC A, s"    equivale a    "A = A - s - CarryFlag" +"sbc a, s"    equivale a    "A = A - s - CarryFlag" 
-"SBC HL, ss"  equivale a    "HL = HL - ss - CarryFlag"+"sbc hl, ss"  equivale a    "HL = HL - ss - CarryFlag"
 </code> </code>
  
Línea 823: Línea 729:
   Instrucción       |S Z H P N C|   Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- SBC A,s            |* * * V 1 *| + sbc a,s            |* * * V 1 *| 
- SBC HL,ss          |* * ? V 1 *|+ sbc hl,ss          |* * ? V 1 *|
 </code> </code>
  
Línea 942: Línea 848:
 Como ya se ha explicado, disponemos de un banco de registros alternativos (los Shadow Registers), y podemos conmutar los valores entre los registros estándar y los alternativos mediante unas determinadas instrucciones del Z80. Como ya se ha explicado, disponemos de un banco de registros alternativos (los Shadow Registers), y podemos conmutar los valores entre los registros estándar y los alternativos mediante unas determinadas instrucciones del Z80.
  
-El Z80 nos proporciona una serie de registros de propósito general (así como un registro de flags), de nombres A, B, C, D, E, F, H y L. El micro dispone también de unos registros extra (set alternativo conocido como Shadow Registers) de nombre A', B', C', D', E', F', H' y L', que aprovecharemos en cualquier momento de nuestro programa. No obstante, no podremos hacer uso directo de estos registros en instrucciones en ensamblador. No es posible, por ejemplo, ninguna de las siguientes instrucciones:+El Z80 nos proporciona una serie de registros de propósito general (así como un registro de flags), de nombres A, B, C, D, E, F, H y L. El micro dispone también de unos registros extra (set alternativo conocido como Shadow Registers) de nombre A', B', C', D', E', F', H' y L', que aprovecharemos en cualquier momento de nuestro programa. No obstante, no podremos hacer uso directo de estos registros en instrucciones en ensamblador. **No es posible**, por ejemplo, usar ninguna de las siguientes instrucciones (porque no existen):
  
 <code> <code>
-LD B', $10 +ld b', $10 
-INC A+inc a
-LD HL', $1234 +ld hl', $1234 
-LD A', ($1234)+ld a', ($1234)
 </code> </code>
  
Línea 955: Línea 861:
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| B | $A0 | B' | $00 |+| B | $a0 | B' | $00 |
 | C | $55 | C' | $00 | | C | $55 | C' | $00 |
 | D | $01 | D' | $00 | | D | $01 | D' | $00 |
-| E | $FF | E' | $00 |+| E | $ff | E' | $00 |
 | H | $00 | H' | $00 | | H | $00 | H' | $00 |
 | L | $31 | L' | $00 | | L | $31 | L' | $00 |
Línea 966: Línea 872:
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| B | $00 | B' | $A0 |+| B | $00 | B' | $a0 |
 | C | $00 | C' | $55 | | C | $00 | C' | $55 |
 | D | $00 | D' | $01 | | D | $00 | D' | $01 |
-| E | $00 | E' | $FF |+| E | $00 | E' | $ff |
 | H | $00 | H' | $00 | | H | $00 | H' | $00 |
 | L | $00 | L' | $31 | | L | $00 | L' | $31 |
Línea 975: Línea 881:
 Si realizamos de nuevo ''EXX'', volveremos a dejar los valores de los registros en sus "posiciones" originales. ''EXX'' (mnemónico ensamblador derivado de EXchange), simplemente intercambia los valores entre ambos bancos. Si realizamos de nuevo ''EXX'', volveremos a dejar los valores de los registros en sus "posiciones" originales. ''EXX'' (mnemónico ensamblador derivado de EXchange), simplemente intercambia los valores entre ambos bancos.
  
-Aparte de la instrucción ''EXX'', disponemos de una instrucción ''EX AFAF<nowiki>'</nowiki>'' , que, como el lector imagina, intercambia los valores de los registros AF y AF'. Así, pasaríamos de:+Aparte de la instrucción ''EXX'', disponemos de una instrucción ''ex afaf<nowiki>'</nowiki>'' , que, como el lector imagina, intercambia los valores de los registros AF y AF'. Así, pasaríamos de:
  
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| A | 01h | A' | 00h +| A | $01 | A' | $00 
-| F | 10h | F' | 00h |+| F | $10 | F' | $00 |
  
 a: a:
Línea 986: Línea 892:
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| A | 00h | A' | 01h +| A | $00 | A' | $01 
-| F | 00h | F' | 10h |+| F | $00 | F' | $10 |
  
-Realizando de nuevo un ''EX AFAF<nowiki>'</nowiki>'' volveríamos a los valores originales en ambos registros.+Realizando de nuevo un ''ex afaf<nowiki>'</nowiki>'' volveríamos a los valores originales en ambos registros.
  
 De esta forma podemos disponer de un set de registros extra Acumulador/Flags con los que trabajar. Por ejemplo, supongamos que programamos una porción de código donde queremos hacer una serie de cálculos entre registros y después dejar el resultado en una posición de memoria, pero no queremos perder los valores actuales de los registros (ni tampoco hacer uso de la pila, que veremos en su momento). En ese caso, podemos hacer: De esta forma podemos disponer de un set de registros extra Acumulador/Flags con los que trabajar. Por ejemplo, supongamos que programamos una porción de código donde queremos hacer una serie de cálculos entre registros y después dejar el resultado en una posición de memoria, pero no queremos perder los valores actuales de los registros (ni tampoco hacer uso de la pila, que veremos en su momento). En ese caso, podemos hacer:
Línea 999: Línea 905:
  
     ; Cambiamos de banco de registros:     ; Cambiamos de banco de registros:
-    EXX +    exx 
-    EX AFAF                      ; Intercambiamos AF con AF'+    ex afaf                      ; Intercambiamos AF con AF'
  
     ; Hacemos nuestras operaciones     ; Hacemos nuestras operaciones
-    LD A, ($1234) +    ld a, ($1234) 
-    LD BA +    ld ba 
-    LD A, ($1235) +    ld a, ($1235) 
-    INC A +    inc a 
-    ADD AB+    add ab
     ; (...etc...)     ; (...etc...)
     ; (...aquí más operaciones...)     ; (...aquí más operaciones...)
  
     ; Grabamos el resultado en memoria     ; Grabamos el resultado en memoria
-    LD ($1236), A+    ld ($1236), a
  
-    ; Recuperamos los valores de los registros +    ; Recuperamos los registros: 
-    EX AFAF                      ; Intercambiamos AF con AF' +    ex afaf                      ; Intercambiamos AF con AF' 
-    EXX+    exx
  
     ; Volvemos al lugar de llamada de la rutina     ; Volvemos al lugar de llamada de la rutina
-    RET+    ret
 </code> </code>
  
Línea 1025: Línea 931:
  
 <code z80> <code z80>
-    EXX+    exx
  
     ; ... Realizamos una serie de operaciones complejas ...     ; ... Realizamos una serie de operaciones complejas ...
  
     ; Guardamos en la pila el valor de HL     ; Guardamos en la pila el valor de HL
-    PUSH HL+    push hl
  
     ; Recuperamos el juego de registros original     ; Recuperamos el juego de registros original
-    EXX+    exx
  
     ; Obtenemos de la pila el valor calculado     ; Obtenemos de la pila el valor calculado
-    POP HL +    pop hl 
-    RET+    ret
 </code> </code>
  
Línea 1044: Línea 950:
 |< 50% >| |< 50% >|
 ^ Instrucción ^ Resultado ^ ^ Instrucción ^ Resultado ^
-EX DEHL | Intercambiar los valores de DE y HL. |  +ex dehl | Intercambiar los valores de DE y HL. |  
-EX (SP), HL | Intercambiar el valor de HL con el valor de 16 bits\\ de la posición de memoria apuntada por el registro SP\\ (por ejemplo, para intercambiar el valor de HL con el\\ del último registro que hayamos introducido en la pila). | +ex (sp), hl | Intercambiar el valor de HL con el valor de 16 bits\\ de la posición de memoria apuntada por el registro SP\\ (por ejemplo, para intercambiar el valor de HL con el\\ del último registro que hayamos introducido en la pila). | 
-EX (SP), IX | Igual que el anterior, pero con IX. | +ex (sp), ix | Igual que el anterior, pero con IX. | 
-EX (SP), IY | Igual que el anterior, pero con IY. |+ex (sp), iy | Igual que el anterior, pero con IY. |
  
 La primera de estas instrucciones nos resultará muy útil en nuestros programas en ensamblador, ya que nos permite intercambiar los valores de los registros DE y HL. Las 3 instrucciones restantes permiten intercambiar el valor apuntado por SP (en memoria) por el valor de los registros HL, IX o IY. La primera de estas instrucciones nos resultará muy útil en nuestros programas en ensamblador, ya que nos permite intercambiar los valores de los registros DE y HL. Las 3 instrucciones restantes permiten intercambiar el valor apuntado por SP (en memoria) por el valor de los registros HL, IX o IY.
  
-Como ya hemos comentado cuando hablamos del carácter Low-Endian de nuestra CPU, al escribir en memoria (también en la pila) primero se escribe el Byte Bajo y luego el Byte Alto. Posteriormente lo leeremos de la misma forma, de tal modo que si los bytes apuntados en la pila (en memoria) son "$FF $00", al hacer el EX (SP), HL, el registro HL valdrá "$00FF".+Como ya hemos comentado cuando hablamos del carácter Low-Endian de nuestra CPU, al escribir en memoria (también en la pila) primero se escribe el Byte Bajo y luego el Byte Alto. Posteriormente lo leeremos de la misma forma, de tal modo que si los bytes apuntados en la pila (en memoria) son **$ff $00**, al hacer el ''ex (sp), hl'', el registro HL valdrá **$00ff**.
  
 Nótese que aprovechando la pila (como veremos en su momento) también podemos intercambiar los valores de los registros mediante: Nótese que aprovechando la pila (como veremos en su momento) también podemos intercambiar los valores de los registros mediante:
  
 <code z80> <code z80>
-PUSH BC +push bc 
-PUSH DE +push de 
-POP BC +pop bc 
-POP DE+pop de
 </code> </code>
  
Línea 1065: Línea 971:
  
 <code z80> <code z80>
-PUSH HL +push hl 
-LD LC +ld lc 
-LD HB +ld hb 
-POP BC+pop bc
 </code> </code>
  
Línea 1078: Línea 984:
 Como hemos visto, con los Shadow Registers tenemos un set de registros adicional donde hacer cálculos, algo que parece en principio maravilloso si necesitamos más registros para realizar operaciones y no queremos acceder ni a memoria ni a la pila para almacenar datos o valores intermedios. Como hemos visto, con los Shadow Registers tenemos un set de registros adicional donde hacer cálculos, algo que parece en principio maravilloso si necesitamos más registros para realizar operaciones y no queremos acceder ni a memoria ni a la pila para almacenar datos o valores intermedios.
  
-Existen algunas restricciones para el uso de los Shadow Registers (pero que como veremos, no nos afectan en el Spectrum): Si la ROM de nuestro sistema, en su rutina de gestión de interrupciones (ISR) utiliza los Shadow Registers, necesitaremos deshabilitar las interrupciones con **DI** (Disable Interrupts) antes de usar "EXX" y volver a habilitarlas con **EI** (Enable Interrupts") cuando hayamos finalizado de trabajar con ellos. Afortunadamente, en el ZX Spectrum, la ISR del modo estándar de interrupciones (IM1, el modo en que funciona el Spectrum desde que lo arrancamos) no utiliza los Shadow Registers.+Existen algunas restricciones para el uso de los Shadow Registers (pero que como veremos, no nos afectan en el Spectrum): Si la ROM de nuestro sistema, en su rutina de gestión de interrupciones (ISR) utiliza los Shadow Registers, necesitaremos deshabilitar las interrupciones con **di** (Disable Interrupts) antes de usar "exx" y volver a habilitarlas con **ei** (Enable Interrupts") cuando hayamos finalizado de trabajar con ellos. Afortunadamente, en el ZX Spectrum, la ISR del modo estándar de interrupciones (im1, el modo en que funciona el Spectrum desde que lo arrancamos) no utiliza los Shadow Registers.
  
 Pero los Shadow Registers tienen una desventaja muy grande y que ya hemos visto, y es que no podemos utilizarlos directamente (no existen instrucciones para operar con ellos) y tampoco podemos usarlos a la vez que los registros normales. Pero los Shadow Registers tienen una desventaja muy grande y que ya hemos visto, y es que no podemos utilizarlos directamente (no existen instrucciones para operar con ellos) y tampoco podemos usarlos a la vez que los registros normales.
Línea 1084: Línea 990:
 Lo único que podemos hacer es intercambiar los registros actuales con los alternativos y viceversa, con lo cual nuestra posibilidad de operar entre los registros normales y los Shadow es muy limitada. Sí, tenemos un set de registros extra para hacer operaciones, pero no podemos pasar directamente los operandos que tenemos actualmente en BC, DE o HL al otro set para hacer dichas operaciones. Lo único que podemos hacer es intercambiar los registros actuales con los alternativos y viceversa, con lo cual nuestra posibilidad de operar entre los registros normales y los Shadow es muy limitada. Sí, tenemos un set de registros extra para hacer operaciones, pero no podemos pasar directamente los operandos que tenemos actualmente en BC, DE o HL al otro set para hacer dichas operaciones.
  
-Debemos almacenar estos valores en la pila, la memoria, o el registro AF (si ejecutamos ''EXX'' pero no ''EX AFAF<nowiki>'</nowiki>'') para que después del intercambio podamos hacer operaciones con estos valores en los nuevos registros. Y para devolvernos el valor resultado de la operación a los registros convencionales tendremos el mismo problema.+Debemos almacenar estos valores en la pila, la memoria, o el registro AF (si ejecutamos ''EXX'' pero no ''ex afaf<nowiki>'</nowiki>'') para que después del intercambio podamos hacer operaciones con estos valores en los nuevos registros. Y para devolvernos el valor resultado de la operación a los registros convencionales tendremos el mismo problema.
  
 Es decir, que usar los Shadow Registers implica utilizar la memoria o la pila, de modo que si ya tenemos que hacer esto... ¿por qué no usar la memoria o la pila directamente para salvaguardar datos de nuestros registros normales cuando lo necesitemos? Es decir, que usar los Shadow Registers implica utilizar la memoria o la pila, de modo que si ya tenemos que hacer esto... ¿por qué no usar la memoria o la pila directamente para salvaguardar datos de nuestros registros normales cuando lo necesitemos?
Línea 1090: Línea 996:
 Salvo excepciones, lo habitual en lugar de utilizar los Shadow Registers como registros extra es utilizar los registros estándar salvaguardando sus valores cuando lo necesitemos en la pila, en la memoria, o (esto es un truco algo más avanzado) en el propio código que se va a ejecutar después. Salvo excepciones, lo habitual en lugar de utilizar los Shadow Registers como registros extra es utilizar los registros estándar salvaguardando sus valores cuando lo necesitemos en la pila, en la memoria, o (esto es un truco algo más avanzado) en el propio código que se va a ejecutar después.
  
-Veamos los 3 ejemplos (sin usar EXX):+Veamos los 3 ejemplos (sin usar exx):
  
 **Opción 1.- Usar la pila:** **Opción 1.- Usar la pila:**
  
 <code z80> <code z80>
-    LD B, 8 +    ld b, 8 
-    PUSH BC              ; Necesitamos salvaguardar BC +    push bc              ; Necesitamos salvaguardar BC 
-    LD B, 9              ; (porque vamos a usarlo para algo)+    ld b, 9              ; (porque vamos a usarlo para algo) 
     ... hacer algo con BC ...     ... hacer algo con BC ...
-    POP BC               ; recuperar el valor de BC+     
 +    pop bc               ; recuperar el valor de BC
 </code> </code>
  
Línea 1105: Línea 1013:
  
 <code z80> <code z80>
-    LD B, 8 +    ld b, 8 
-    LD (1000), BC        ; Necesitamos salvaguardar BC +    ld (1000), bc        ; Necesitamos salvaguardar BC 
-    LD B,9               ; (porque vamos a usarlo para algo)+    ld b,9               ; (porque vamos a usarlo para algo) 
 +    
     .....     .....
-    LD (1002), BC        ; guardamos el resultado +     
-    LD BC, (1000)        ; restauramos el valor de BC+    ld (1002), bc        ; guardamos el resultado 
 +    ld bc, (1000)        ; restauramos el valor de BC
     ....     ....
 </code> </code>
Línea 1119: Línea 1029:
  
 <code z80> <code z80>
-    LD (save_bc+1), BC    ; Escribimos BC en la parte NN NN del +    ld (save_bc+1), bc    ; Escribimos BC en la parte NN NN del 
-                          ; opcode "LD BC, NN NN" en memoria+                          ; opcode "ld bc, NN NN" en memoria
  
     ... hacer cosas con BC, perdiendo su valor ...     ... hacer cosas con BC, perdiendo su valor ...
  
 save_bc: save_bc:
-    LD BC, $0000          ; En el LD anterior cambiamos $000+    ld bc, $0000          ; En el LD anterior cambiamos $000
                           ; por el valor de BC, así que cuando                           ; por el valor de BC, así que cuando
-                          ; el z80 llegue aquí no es ya LD BC, 0 +                          ; el z80 llegue aquí no es ya ld bc, 0 
-                          ; sino LD BC, valor_que_tenia_BC+                          ; sino ld bc, valor_que_tenia_BC
                           ; así que recuperaremos BC aquí.                           ; así que recuperaremos BC aquí.
 </code> </code>
  
-El ejemplo anterior es muy interesante. Cuando hacemos el ''LD (save_bc+1), BC'', estamos sobreescribiendo nuestro propio programa. Concretamente, lo que hacemos es CAMBIAR el opcode que estaba ensamblado (''LD BC, $0000'', que en memoria sería "**$01 $00 $00**") por **$01 XX XX**.+El ejemplo anterior es muy interesante. Cuando hacemos el ''ld (save_bc+1), bc'', estamos sobreescribiendo nuestro propio programa. Concretamente, lo que hacemos es CAMBIAR el opcode que estaba ensamblado (''ld bc, $0000'', que en memoria sería "**$01 $00 $00**") por **$01 XX XX**.
  
-En este caso ''save_bc'' apuntaría al $01, y con ''save_bc+1'' lo que hacemos es escribir después del opcode de ''LD BC,'', en la parte del opcode que contiene el valor a cargar en el registro. Cuando se ejecuta el ''LD (save_bc+1), BC'', estamos escribiendo el valor de BC en ese momento encima de "XX XX" de forma que cuando la ejecución del programa continúe y lleguemos a ese punto, el comando ''LD BC, XX'' se ejecutará y recuperará en BC el valor que tenía BC cuando se almacenó en esa posición de memoria.+En este caso ''save_bc'' apuntaría al $01, y con ''save_bc+1'' lo que hacemos es escribir después del opcode de ''ld bc,'', en la parte del opcode que contiene el valor a cargar en el registro. Cuando se ejecuta el ''ld (save_bc+1), bc'', estamos escribiendo el valor de BC en ese momento encima de "XX XX" de forma que cuando la ejecución del programa continúe y lleguemos a ese punto, el comando ''ld bc, XX'' se ejecutará y recuperará en BC el valor que tenía BC cuando se almacenó en esa posición de memoria.
  
-Con esto, estamos preservando el valor del registro a cambio de una escritura en memoria (''LD (nn), BC'' = 20 ciclos de reloj) y de su posterior asignación (10 ciclos), un total de 30 ciclos de reloj. La alternativa de PUSH POP utilizaría 21 ciclos en total (11 el ''PUSH'' y 10 el ''POP'').+Con esto, estamos preservando el valor del registro a cambio de una escritura en memoria (''ld (nn), bc'' = 20 ciclos de reloj) y de su posterior asignación (10 ciclos), un total de 30 ciclos de reloj. La alternativa con PUSH/POP utilizaría 21 ciclos en total (11 el ''PUSH'' y 10 el ''POP'').
  
 \\  \\ 
Línea 1150: Línea 1060:
 ===== Ficheros ===== ===== Ficheros =====
  
-   * {{cursos:ensamblador:03_ejemplo.asm|Ejemplo de programa en ASM}} 
-   * {{cursos:ensamblador:03_ejemplo.tap|Fichero tap del ejemplo ejemplo.asm}} 
    * {{cursos:ensamblador:03_cambio.asm|Programa en ASM que muestra el uso de la pila para el intercambio de registros}}    * {{cursos:ensamblador:03_cambio.asm|Programa en ASM que muestra el uso de la pila para el intercambio de registros}}
    * {{cursos:ensamblador:03_cambio.tap|Fichero tap del ejemplo cambio.asm}}    * {{cursos:ensamblador:03_cambio.tap|Fichero tap del ejemplo cambio.asm}}
  • cursos/ensamblador/lenguaje_1.1705140846.txt.gz
  • Última modificación: 13-01-2024 10:14
  • por sromero