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 [09-12-2010 18:04] sromerocursos:ensamblador:lenguaje_1 [22-01-2024 07:51] (actual) – [Instrucciones LD (instrucciones de carga)] sromero
Línea 1: Línea 1:
 ====== Lenguaje Ensamblador del Z80 (I) ====== ====== Lenguaje Ensamblador del Z80 (I) ======
  
-\\  +=====Arquitectura del Z80 e Instrucciones básicas ======
-===== La Arquitectura del Spectrum ===== +
- +
- En esta 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 en PASMO ===== +
- +
-En el primer capítulo 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 (como ya vimosen 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: +
-    * Para 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" +
-    * Para generar un fichero .tap directamente ejecutable (de forma que sea pasmo quien añada el cargador BASIC), lo ensamblamos con "pasmo --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 (por ejemplo) .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 compilación (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 sin problemas. +
-    * Además de una instrucción por línea podremos añadir etiquetas (para referenciar a dicha línea, algo que veremos posteriormente) y comentarios (con ';'). +
-\\  +
-  * **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, podemos partir el fichero en varios ficheros 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 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. +
-    * 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 PASMO, cualquier referencia a una etiqueta a lo largo del programa se convierte en una referencia a la posición de memoria donde se ensambla la instrucción o dato donde hemos colocado la etiqueta. Podemos utilizar así etiquetas para hacer referencia a nuestros gráficos, variables, datos, funciones, lugares a donde saltar, etc. +
-\\  +
-  * **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, END para finalizar el programa, IF/ELSE/ENDIF en tiempo de compilación, INCLUDE e INCBIN, MACRO y REPT. +
-    * La directiva END permite indicar un parámetro numérico (END XXXX) que "pasmo --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"+
-    * 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. +
-    * 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. +
-\\  +
-  * **[[http://www.arrakis.es/~ninsesabe/pasmo/pasmodoc.html#operators|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. +
- +
-\\  +
-===== 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 +
- +
-valor     EQU +
-destino   EQU  18384 +
- +
-  ; 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 +
- +
-datos DEFB 127, %10101010, 0, 128, $FE, %10000000, FFh +
- +
-END +
-</code> +
- +
-Algunos detalles a tener en cuenta: +
- +
-  * Como veis, se pone una instrucción por línea. +
-  * Los comentarios pueden ir en sus propias líneas, o dentro de líneas de instrucciones (tras ellas). +
-  * 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, 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 "DJNZ bucle" que un "DJNZ 40008", 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. +
-  * 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 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 |Salida del ejemplo 1}}+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.
  
-Los píxeles que aparecen en el centro de la pantalla 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 del programa por END 40000no tendréis la necesidad de ejecutar RANDOMIZE USR 40000 y que pasmo lo introducirá en el listado BASIC de "arranque".+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 150: Línea 19:
     * **B, C, D, E, H, L**: Registros de propósito general, utilizables para gran cantidad de operaciones, almacenamiento de valores, etc.     * **B, C, D, E, H, L**: Registros de propósito general, utilizables para gran cantidad de operaciones, almacenamiento de valores, etc.
     * **I**: Registro de interrupción, no lo utilizaremos en nuestros primeros programas. No debemos modificar su valor, aunque en el futuro veremos su uso en las interrupciones del Spectrum.     * **I**: Registro de interrupción, no lo utilizaremos en nuestros primeros programas. No debemos modificar su valor, aunque en el futuro veremos su uso en las interrupciones del Spectrum.
-    * **R**: Registro de Refresco de memoria: lo utiliza internamente la CPU para saber cuándo debe refrescar la RAM. Su valor cambia sólo conforme el Z80 va ejecutando instrucciones, de modo que podemos utilizarlo (leerlo) para obtener valores pseudo-aleatorios entre 0 y 127 (el Z80 no cambia el bit de mayor peso de R, sólo los bits del 0 al 6. El bit 7 se puede modificar manualmente cargando un valor directamente en este registro, por ejemplo con "LD A, R").+    * **R**: Registro de Refresco de memoria: lo utiliza internamente la CPU para saber cuándo debe refrescar la RAM. Su valor cambia sólo conforme el Z80 va ejecutando instrucciones, de modo que podemos utilizarlo (leerlo) para obtener valores pseudo-aleatorios entre 0 y 127 (el Z80 no cambia el bit de mayor peso de R, sólo los bits del 0 al 6).
 \\  \\ 
 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 FFh y F vale 00h, AF valdrá automáticamente "FF00h"+    * **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.
 \\  \\ 
 Aparte de estos registros, existen otra serie de registros de 16 bits: Aparte de estos registros, existen otra serie de registros de 16 bits:
 \\  \\ 
-    * **IX, IY**: Dos registros de 16 bits pensados para acceder a memoria de forma indexada. Gracias a estos registros podemos realizar operaciones como: "LD (IX+desplazamiento), VALOR". Este tipo de registros se suele utilizar pues para hacer de índices dentro de tablas o vectores. El desplazamiento es un valor numérico de 8 bits en complemento a 2, lo que nos permite un rango desde -128 a +127 (puede ser negativo para acceder a posiciones de memoria anteriores a IX).+    * **IX, IY**: Dos registros de 16 bits pensados para acceder a memoria de forma indexada. Gracias a estos registros podemos realizar operaciones como: ''LD (IX+desplazamiento), VALOR''. Este tipo de registros se suele utilizar pues para hacer de índices dentro de tablas o vectores. El desplazamiento es un valor numérico de 8 bits en complemento a 2, lo que nos permite un rango desde -128 a +127 (puede ser negativo para acceder a posiciones de memoria anteriores a IX).
     * **SP**: Puntero de pila, como veremos en su momento apunta a la posición actual de la "cabeza" de la pila.     * **SP**: Puntero de pila, como veremos en su momento apunta a la posición actual de la "cabeza" de la pila.
-    * **PC**: Program Counter o Contador de Programa. Como ya vimos en la anterior entrega, contiene la dirección de la instrucción actual a ejecutar. No modificaremos PC directamente moviendo valores a este registro, sino que lo haremos mediante instrucciones de salto (JP, JR, CALL...).+    * **PC**: Program Counter o Contador de Programa. Como ya vimos en la anterior entrega, contiene la dirección de la instrucción actual a ejecutar. No modificaremos PC directamente moviendo valores a este registro, sino que lo haremos mediante instrucciones de salto (''JP''''JR''''CALL''...).
 \\  \\ 
-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 la instrucción de ensamblador "EXX"como veremos más adelante. 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 EXX se quedarán en los registros Shadow), y después recuperar los valores originales volviendo a ejecutar un EXX.+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 AFAF<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 178: 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'' ''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 200: Línea 69:
  
 <code z80> <code z80>
-  LD BC1234h +    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 212: 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: 
 +                  ; 
 +                  ; ld b, d 
 +                  ; ld c, e
  
- 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 238: Línea 112:
  
 No os preocupéis: es sólo una cuestión de práctica. Tras haber realizado varios programas en ensamblador ya conoceréis, prácticamente de memoria, qué instrucciones son válidas para el microprocesador y cuáles no. No os preocupéis: es sólo una cuestión de práctica. Tras haber realizado varios programas en ensamblador ya conoceréis, prácticamente de memoria, qué instrucciones son válidas para el microprocesador y cuáles no.
 +
 +\\ 
 +==== IY Y LA ROM DEL SPECTRUM ====
 +
 +Aunque IY es un registro de 16 bits como los demás, en el caso del Spectrum no está disponible para su uso a menos que se cumplan ciertas condiciones.
 +
 +La ROM del Spectrum (el "sistema operativo" que ejecuta BASIC) realiza ciertas tareas 50 veces por segundo. Estas tareas son transparentes por nuestro programa, ya que el Spectrum "genera" lo que se conocen como "interrupciones" de forma periódica.
 +
 +Estas "interrupciones" detienen el programa durante un brevísimo período de tiempo para realizar tareas como leer el teclado y actualizar ciertas variables del sistema cuyos valores residen en memoria. El acceso a esas variables del sistema se realiza con el registro IY, que la ROM del Spectrum espera que no sea modificado por nadie ya que lo usa como un "puntero base" para acceder a las variables, referenciándolas no por su dirección de memoria sino por (IY+$30), por ejemplo.
 +
 +Debido a esto, no debemos modificar el registro IY, a menos que cumplamos estas 2 condiciones:
 +
 +  - Cambiamos el modo de funcionamiento del Spectrum de im1 (su modo por defecto) a im2 (un modo "personalizado").
 +  - 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.
 +
  
 \\  \\ 
 ===== El registro de flags ===== ===== El registro de flags =====
  
-Hemos hablado del registro de 8 bits F como un registro especial. La particularidad de //F// es que //no es un registro de propósito general// donde podamos introducir valores a voluntad, sino que los diferentes bits del registro F tienen un significado propio que cambia automáticamente según el resultado de operaciones anteriores.+Hemos hablado del registro de 8 bits **F** como un registro especial. La particularidad de //F// es que //no es un registro de propósito general// donde podamos introducir valores a voluntad, sino que los diferentes bits del registro F tienen un significado propio que cambia automáticamente según el resultado de operaciones anteriores.
  
 Por ejemplo, uno de los bits del registro F, el bit nº 6, es conocido como "Zero Flag", y nos indica si el resultado de la última operación (para determinadas operaciones, como las aritméticas o las de comparación) es cero o no es cero. Si el resultado de la anterior operación resultó cero, este FLAG se pone a uno. Si no resultó cero, el flag se pone a cero. Por ejemplo, uno de los bits del registro F, el bit nº 6, es conocido como "Zero Flag", y nos indica si el resultado de la última operación (para determinadas operaciones, como las aritméticas o las de comparación) es cero o no es cero. Si el resultado de la anterior operación resultó cero, este FLAG se pone a uno. Si no resultó cero, el flag se pone a cero.
Línea 250: Línea 141:
 Veamos los diferentes registros de flags (bits del registro F) y su utilidad: Veamos los diferentes registros de flags (bits del registro F) y su utilidad:
  
 +\\ 
 {{ cursos:ensamblador:registrof.gif |Los indicadores de flag del registro F}} {{ cursos:ensamblador:registrof.gif |Los indicadores de flag del registro F}}
 +\\ 
  
 +\\ 
     * **Flag S (sign o signo)**: Este flag se pone a uno si el resultado de la operación realizada en complemento a dos es negativo (es una copia del bit más significativo del resultado). Si por ejemplo realizamos una suma entre 2 números en complemento a dos y el resultado es negativo, este bit se pondrá a uno. Si el resultado es positivo, se pondrá a cero. Es útil para realizar operaciones matemáticas entre múltiples registros: por ejemplo, si nos hacemos una rutina de multiplicación o división de números que permita números negativos, este bit nos puede ser útil en alguna parte de la rutina.     * **Flag S (sign o signo)**: Este flag se pone a uno si el resultado de la operación realizada en complemento a dos es negativo (es una copia del bit más significativo del resultado). Si por ejemplo realizamos una suma entre 2 números en complemento a dos y el resultado es negativo, este bit se pondrá a uno. Si el resultado es positivo, se pondrá a cero. Es útil para realizar operaciones matemáticas entre múltiples registros: por ejemplo, si nos hacemos una rutina de multiplicación o división de números que permita números negativos, este bit nos puede ser útil en alguna parte de la rutina.
-    * **Flag Z (zero o cero)**: Este flag se pone a uno si el resultado de la última operación que afecte a los flags es cero. Por ejemplo, si realizamos una operación matemática y el resultado es cero, se pondrá a uno. Este flag es uno de los más útiles, ya que podemos utilizarlo para múltiples tareas. La primera es para los bucles, ya que podremos programar código como: 
  
 +    * **Flag Z (zero o cero)**: Este flag se pone a uno si el resultado de la última operación que afecte a los flags es cero. Por ejemplo, si realizamos una operación matemática y el resultado es cero, se pondrá a uno. Este flag es uno de los más útiles, ya que podemos utilizarlo para múltiples tareas. La primera es para los bucles, ya que podremos programar código como:
  
 <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),  +             
-            ; saltar a la etiqueta bucle y continuar. DEC B hará que el flag Z  +            ; Si el resultado de la operación anterior no es cero (NZ = Non Zero), 
-            ; se ponga a 1 cuando B llegue a cero, lo que afectará al JR NZ.+            ; 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.
             ; 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 283: 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 299: Línea 194:
 </code> </code>
  
-    * **Flag H (Half-carry o Acarreo-BCD)**: Se pone a uno cuando en operaciones BCD existe un acarreo del bit 3 al bit 4. Es muy probable que no lleguemos a utilizarlo nunca.+ Existe una instrucción específica para realizar comparaciones: ''CP'', que es similar a ''SUB'' pero que no altera el valor de A. Hablaremos de ''CP'' con más detalle en su momento. 
 + 
 +    * **Flag H (Half-carry o Acarreo-BCD)**: Se pone a uno cuando en operaciones BCD existe un acarreo del bit 3 al bit 4.
  
     * **Flag P/V (Parity/Overflow o Paridad/Desbordamiento)**: En las operaciones que modifican el bit de paridad, este bit vale 1 si el número de unos del resultado de la operación es par, y 0 si es impar. Si, por contra, el resultado de la operación realizada necesita más bits para ser representado de los que nos provee el registro, tendremos un desbordamiento, con este flag a 1. Este mismo bit sirve pues para 2 tareas, y nos indicará una u otra (paridad o desbordamiento) según sea el tipo de operación que hayamos realizado. Por ejemplo, tras una suma, su utilidad será la de indicar el desbordamiento. \\ El flag de desbordamiento se activará cuando en determinadas operaciones pasemos de valores 11111111b a 00000000b, por "falta de bits" para representar el resultado o viceversa . Por ejemplo, en el caso de INC y DEC con registros de 8 bits, si pasamos de 0 a 255 o de 255 a 0.     * **Flag P/V (Parity/Overflow o Paridad/Desbordamiento)**: En las operaciones que modifican el bit de paridad, este bit vale 1 si el número de unos del resultado de la operación es par, y 0 si es impar. Si, por contra, el resultado de la operación realizada necesita más bits para ser representado de los que nos provee el registro, tendremos un desbordamiento, con este flag a 1. Este mismo bit sirve pues para 2 tareas, y nos indicará una u otra (paridad o desbordamiento) según sea el tipo de operación que hayamos realizado. Por ejemplo, tras una suma, su utilidad será la de indicar el desbordamiento. \\ El flag de desbordamiento se activará cuando en determinadas operaciones pasemos de valores 11111111b a 00000000b, por "falta de bits" para representar el resultado o viceversa . Por ejemplo, en el caso de INC y DEC con registros de 8 bits, si pasamos de 0 a 255 o de 255 a 0.
Línea 309: Línea 206:
 Así pues, resumiendo: Así pues, resumiendo:
  
-    * El registro F es un registro especial cuyo valor no manejamos directamente, sino que cada uno de sus bits tiene un valor especial y está a 1 o a 0 según ciertas condiciones de la última operación realizada que afecte a dicho registro.+    * El registro F es un registro cuyo valor no manejamos directamente, sino que cada uno de sus bits tiene un valor especial y está a 1 o a 0 según ciertas condiciones de la última operación realizada que afecte a dicho registro.
     * Por ejemplo, si realizamos una operación y el resultado de la misma es cero, se pondrá a 1 el flag de Zero (Z) del registro F, que no es más que su bit número 6.     * Por ejemplo, si realizamos una operación y el resultado de la misma es cero, se pondrá a 1 el flag de Zero (Z) del registro F, que no es más que su bit número 6.
     * No todas las operaciones afectan a los flags, iremos viendo qué operaciones afectan a qué flags conforme avancemos en el curso, en el momento en que se estudia cada instrucción.     * No todas las operaciones afectan a los flags, iremos viendo qué operaciones afectan a qué flags conforme avancemos en el curso, en el momento en que se estudia cada instrucción.
Línea 315: Línea 212:
  
 \\  \\ 
-===== Instrucciones LD =====+===== Instrucciones LD (instrucciones de carga) =====
  
-Las operaciones que más utilizaremos en nuestros programas en ensamblador serán sin duda las operaciones de carga o instrucciones **LD**. Estas operaciones sirven para:+Las operaciones que más utilizaremos en nuestros programas en ensamblador serán sin duda las operaciones de carga o instrucciones ''LD''. Estas operaciones sirven para:
  
     * Meter un valor en un registro.     * Meter un valor en un registro.
Línea 325: Línea 222:
     * Asignarle a un registro el contenido de una dirección de memoria.     * Asignarle a un registro el contenido de una dirección de memoria.
  
-La sintaxis de LD en lenguaje ensamblador es:+La sintaxis de ''LD'' en lenguaje ensamblador es:
  
 <code z80> <code z80>
- LD DESTINO, ORIGEN+ld DESTINO, ORIGEN
 </code> </code>
  
Línea 334: Línea 231:
  
     * Asignar a un registro un valor numérico directo de 8 o 16 bits.     * Asignar a un registro un valor numérico directo de 8 o 16 bits.
 +
 <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>
  
     * Copiar el contenido de un registro a otro registro:     * Copiar el contenido de un registro a otro registro:
 +
 <code z80> <code z80>
-        LD AB          ; A = B +ld ab               ; A = B 
-        LD BCDE        ; BC = DE+ld bcde             ; BC = DE
 </code> </code>
 +
     * Escribir en posiciones de memoria:     * Escribir en posiciones de memoria:
 +
 <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>
 +
     * Leer el contenido de posiciones de memoria:     * Leer el contenido de posiciones de memoria:
 +
 +<code z80>
 +ld a, (12345)         ; A = valor en Memoria[12345]
 +ld b, (hl)            ; B = valor en Memoria[valor de HL]
 +</code>
 +
 + 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> <code z80>
-        LD A, (12345)    ; A valor en Memoria[12345] +ld (16384), hl    =>    POKE 16384, L 
-        LD B(HL)       ; B = valor en Memoria[valor de HL]+                        POKE 16385H
 </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. 
  
 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 373: Línea 313:
  
 <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 403: Línea 343:
  
 <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 419: Línea 359:
  
 <code z80> <code z80>
- ; 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>
  
-Haré 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" "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'' ''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 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.
  
 \\  \\ 
-===== CPU Z80: Low Endian =====+===== Tamaños y ciclos =====
  
-Un detalle más sobre nuestra CPU: a la hora de trabajar con datos de 16 bits (por ejemplo, leer o escribir de memoria) conviene tener en cuenta que nuestro Z80 es una CPU del tipo //LOW-ENDIAN//es decir, que si almacenamos en la posición de memoria 0000h el valor "1234h"el contenido de las celdillas de memoria sería:+Hay otros dos datos que, como la afectación de flagsson muy importantes sobre las diferentes instrucciones que iremos viendo.
  
-^ Posición ^ Valor ^ +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).
-| 0000h | 34h |  +
-| 0001h | 12h |+
  
-En otro tipo de procesadores del tipo //BIG-ENDIAN//, los bytes aparecerían escritos en memoria de la siguiente forma:+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''.
  
-^ Posición ^ Valor ^  +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.
-| 0000h | 12h | +
-| 0001h | 34h |+
  
-Debemos tener en cuenta este dato a la hora de escribir valores de 16 bits en memoria y recuperarlos posteriormente mediante operaciones de acceso a la memoria.+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 en la celdilla siguiente el byte más significativoEs decir, que el opcode correspondiente a ''ld bc, $1234'' en memoria no es "**$01 $12 $34**" sino "**$01 $34 $12**";
  
 \\  \\ 
 ===== Incrementos y decrementos ===== ===== Incrementos y decrementos =====
  
-Entre las operaciones disponibles, tenemos la posibilidad de incrementar (**INC**) y decrementar (**DEC**) en 1 unidad el contenido de determinados registros de 8 y 16 bits, así como de posiciones de memoria apuntadas por HL o por IX/IY más un offset (desplazamiento de 8 bits).+Entre las operaciones disponibles, tenemos la posibilidad de incrementar (''INC'') y decrementar (''DEC'') en 1 unidad el contenido de determinados registros de 8 y 16 bits, así como de posiciones de memoria apuntadas por HL o por IX/IY más un offset (desplazamiento de 8 bits).
  
 Por ejemplo: Por ejemplo:
  
 <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>
  
-Veamos las operaciones INC y DEC permitidas:+Veamos las operaciones ''INC'' ''DEC'' permitidas:
  
 <code z80> <code z80>
-   INC +inc 
-   DEC +dec 
-   INC rr +inc rr 
-   DEC rr+dec rr
 </code> </code>
  
Línea 523: Línea 464:
  
 <code z80> <code z80>
-   INC (HL+inc (hl
-   DEC (HL)+dec (hl)
 </code> </code>
  
Línea 530: 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 541: 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 (FFh) 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 (FFFFh)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 (FFh). +    * Si un registro de 8 bits vale 0 y lo decrementamos, pasará a valer 255 ($ff). 
-    * Si un registro de 16 bits vale 0 (0h) y lo decrementamos, pasará a valer 65535 (FFh).+    * 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).
  
-Lo siguiente que vamos a ver es una tabla de afectación de flags (que podréis ver en muchas tablas de instrucciones del Z80, y a las que conviene que os vayáis acostumbrando). Esta tabla indica cómo afecta cada instrucción a cada uno de los flags:+Lo siguiente que vamos a ver es una tabla de afectación de flags (que encontraremos en muchas tablas de instrucciones del Z80, y a las que conviene ir acostumbrandose). Esta tabla indica cómo afecta cada instrucción a cada uno de los flags:
  
 <code> <code>
Línea 566: 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 597: Línea 538:
 ===== Operaciones matematicas ===== ===== Operaciones matematicas =====
  
-Las operaciones aritméticas básicas para nuestro Spectrum son la suma y la resta, tanto con acarreo como sin él. A partir de ellas deberemos crearnos nuestras propias rutinas para multiplicar, dividir, etc.+ Las operaciones aritméticas básicas para nuestro Spectrum son la suma y la resta, tanto con acarreo como sin él. A partir de ellas deberemos crearnos nuestras propias rutinas para multiplicar, dividir, etc.
  
 \\  \\ 
 ==== Suma: ADD (Add) ==== ==== Suma: ADD (Add) ====
  
-Nuestro microprocesador Z80 puede realizar sumas de 8 y 16 bits internamente. La instrucción utilizada para ello es "**ADD**" y el formato es:+Nuestro microprocesador Z80 puede realizar sumas de 8 y 16 bits internamente. La instrucción utilizada para ello es ''ADD'' y el formato es:
  
 <code z80> <code z80>
- ADD DESTINO, ORIGEN+add DESTINO, ORIGEN
 </code> </code>
  
Línea 611: 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 619: Línea 560:
  
 <code> <code>
- s:  Cualquier registro de 8 bits (A, B, C, D, E, H, L),  + s:  Cualquier registro de 8 bits (A, B, C, D, E, H, L), 
-     cualquier valor inmediato de 8 bits (en el rango 0-255 o -128+127  +     cualquier valor inmediato de 8 bits (en el rango 0-255 o -128+127 
-     en complemento a dos), cualquier dirección de memoria apuntada por  +     en complemento a dos), cualquier dirección de memoria apuntada por 
-     HL, y cualquier dirección de memoria apuntada por un registro +     HL, y cualquier dirección de memoria apuntada por un registro
      índice con desplazamiento de 8 bits.      índice con desplazamiento de 8 bits.
  ss: Cualquier registro de 16 bits de entre los siguientes: BC, DE, HL, SP.  ss: Cualquier registro de 16 bits de entre los siguientes: BC, DE, HL, SP.
  ri: Uno de los 2 registros índices (IX o IY).  ri: Uno de los 2 registros índices (IX o IY).
- rr: Cualquier registro de 16 bits de entre los siguientes excepto el mismo + rr: Cualquier registro de 16 bits de entre los siguientes excepto el mismo
      registro índice origen: BC, DE, HL, IX, IY, SP.      registro índice origen: BC, DE, HL, IX, IY, SP.
 </code> </code>
Línea 633: 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 653: 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"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'' ''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 669: 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 677: Línea 619:
  
 <code> <code>
- 0 + 0 = 0 +0 + 0 = 0 
- 0 + 1 = 1 +0 + 1 = 1 
- 1 + 0 = 1 +1 + 0 = 1 
- 1 + 1 = 10 (=0 con acarreo)+1 + 1 = 10 (=0 con acarreo)
 </code> </code>
  
Línea 700: 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 710: 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.) 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 719: 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 728: 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>
  
-Es importante recordar que en una operación "SUB X", la operación realizada es "A=A-X" y no "A=X-A".+Es importante recordar que en una operación ''SUB X'', la operación realizada es "A=A-X" y no "A=X-A".
  
-Por otra parte, con respecto a la afectación de flags, tenemos la siguiente:+Por otra parte, con respecto a la afectación de flags, es la siguiente:
  
 <code> <code>
Línea 749: Línea 691:
 ==== Suma con acarreo: ADC (Add with carry) ==== ==== Suma con acarreo: ADC (Add with carry) ====
  
-Sumar con acarreo dos elementos (**ADC**) significa realizar la suma de uno con el otro y, posteriormente, sumarle el estado del flag de Carry. Es decir:+Sumar con acarreo dos elementos (''ADC'') significa realizar la suma de uno con el otro y, posteriormente, sumarle el estado del flag de Carry. Es decir:
  
 <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 764: 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>
  
-La suma con acarreo se utiliza normalmente para sumar las partes altas de palabras de 16 bytes. Se suma la parte baja con ADD y luego la parte alta con ADC para tener en cuenta el acarreo de la suma de la parte baja.+La suma con acarreo se utiliza normalmente para sumar las partes altas de elementos de 16 bits. Se suma la parte baja con ''ADD'' y luego la parte alta con ''ADC'' para tener en cuenta el acarreo de la suma de la parte baja. 
  
 \\  \\ 
 ==== Resta con acarreo: SBC (Substract with carry) ==== ==== Resta con acarreo: SBC (Substract with carry) ====
  
-Al igual que en el caso de la suma con acarreo, podemos realizar restas con acarreo (**SBC**), que no son más que realizar una resta de los 2 operandos, tras lo cual restamos además el valor del bit de Carry Flag:+Al igual que en el caso de la suma con acarreo, podemos realizar restas con acarreo (''SBC''), que no son más que realizar una resta de los 2 operandos, tras lo cual restamos además el valor del bit de Carry Flag:
  
 <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 786: 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 829: Línea 772:
 </code> </code>
  
-Como veremos en unos minutos, se eligió este sistema para representar los números negativos para que las operaciones matemáticas estándar funcionaran directamente sobre los números positivos y negativos. ¿Por qué no utilizamos directamente la inversión de los bits para representar los números negativos y estamos sumando además 1 para obtenerlos? Sencillo: si no sumáramos uno y simplemente invirtiéramos los bits, tendríamos 2 ceros (00000000 y 11111111) y además las operaciones matemáticas no cuadrarían (por culpa de los dos ceros). La gracia del complemento a dos es que las sumas y restas binarias lógicas (ADD, ADC, SUB y SBC) funcionan:+Se eligió este sistema para representar los números negativos para que las operaciones matemáticas estándar funcionaran directamente sobre los números positivos y negativos. ¿Por qué no utilizamos directamente la inversión de los bits para representar los números negativos y estamos sumando además 1 para obtenerlos? Sencillo: si no sumáramos uno y simplemente invirtiéramos los bits, tendríamos 2 ceros (00000000 y 11111111) y además las operaciones matemáticas no cuadrarían (por culpa de los dos ceros). La gracia del complemento a dos es que las sumas y restas binarias lógicas (ADD, ADC, SUB y SBC) funcionan:
  
  Sumemos -17 y 32:  Sumemos -17 y 32:
Línea 868: Línea 811:
 Como acabamos de ver, en complemento a dos el último bit (el bit 7) nos indica el signo, y cuando operamos con 2 números binarios que nosotros interpretamos como números en complemento a dos no nos basta con el bit de Carry. Es el bit de Overflow el que nos dará información sobre el desbordamiento a un nivel lógico. Como acabamos de ver, en complemento a dos el último bit (el bit 7) nos indica el signo, y cuando operamos con 2 números binarios que nosotros interpretamos como números en complemento a dos no nos basta con el bit de Carry. Es el bit de Overflow el que nos dará información sobre el desbordamiento a un nivel lógico.
  
-En pocas palabras, el bit de Carry se activará si pasamos de 255 a 0 o de 0 a 255 (comportándose como un bit de valor 2 elevado a 8, o 256), y el bit de overflow lo hará si el resultado de una operación en complemento a dos requiere más de 7 bits para ser representado.+ El bit de Carry se activará si pasamos de 255 a 0 o de 0 a 255 (comportándose como un bit de valor 2 elevado a 8, o 256), y el bit de overflow lo hará si el resultado de una operación en complemento a dos requiere más de 7 bits para ser representado.
  
 Mediante ejemplos: Mediante ejemplos:
Línea 898: Línea 841:
  
 En el ejemplo anterior, V se activa porque no ha habido desbordamiento físico (no es necesario un bit extra para representar la operación), pero sí lógico: no podemos representar +128 con 7 bits+signo en complemento a dos. En el ejemplo anterior, V se activa porque no ha habido desbordamiento físico (no es necesario un bit extra para representar la operación), pero sí lógico: no podemos representar +128 con 7 bits+signo en complemento a dos.
 +
  
 \\  \\ 
Línea 904: 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>
  
-La manera de utilizar estos registros alternativos es conmutar sus valores con los registros estándar mediante la instrucción "**EXX**", cuyo resultado es el intercambio de B por B', C por C', D por D', E por E', H por H' y L por L'. Supongamos que tenemos los siguientes valores en los registros: +La manera de utilizar estos registros alternativos es conmutar sus valores con los registros estándar mediante la instrucción ''EXX'', cuyo resultado es el intercambio de B por B', C por C', D por D', E por E', H por H' y L por L'. Supongamos que tenemos los siguientes valores en los registros:
  
 +|< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| B | A0h | B' | 00h +| B | $a0 | B' | $00 
-| C | 55h | C' | 00h +| C | $55 | C' | $00 
-| D | 01h | D' | 00h +| D | $01 | D' | $00 
-| E | FFh | E' | 00h +| E | $ff | E' | $00 
-| H | 00h | H' | 00h +| H | $00 | H' | $00 
-| L | 31h | L' | 00h |+| L | $31 | L' | $00 |
  
-En el momento en que realicemos un EXX, los registros cambiarán de valor por la "conmutación" de bancos:+En el momento en que realicemos un ''EXX'', los registros cambiarán de valor por la "conmutación" de bancos:
  
 +|< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| B | 00h | B' | A0h +| B | $00 | B' | $a0 
-| C | 00h | C' | 55h +| C | $00 | C' | $55 
-| D | 00h | D' | 01h +| D | $00 | D' | $01 
-| E | 00h | E' | FFh +| E | $00 | E' | $ff 
-| H | 00h | H' | 00h +| H | $00 | H' | $00 
-| L | 00h | L' | 31h |+| L | $00 | L' | $31 |
  
-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'** , 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% >|
 ^ 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:
  
 +|< 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' 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 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:
  
 <code z80> <code z80>
Línea 958: 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, (1234h+    ld a, ($1234
-    LD BA +    ld ba 
-    LD A, (1235h+    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 (1236h), 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>
  
-Aparte de EXX y EX AFAFtenemos disponibles 3 instrucciones de intercambio más que no trabajan con los registros alternativos, sino entre la memoria y registros, y la pila (o memoria en general) y los registros HL, IX e IY.+También podríamos utilizar la pila para almacenar valores como resultado de un cálculoantes de hacer ''EXX'':
  
 +<code z80>
 +    exx
 +
 +    ; ... Realizamos una serie de operaciones complejas ...
 +
 +    ; Guardamos en la pila el valor de HL
 +    push hl
 +
 +    ; Recuperamos el juego de registros original
 +    exx
 +
 +    ; Obtenemos de la pila el valor calculado
 +    pop hl
 +    ret
 +</code>
 +
 +Además de ''EXX'' y ''EX AF, AF<nowiki>'</nowiki>'' tenemos disponibles 3 instrucciones de intercambio más que no trabajan con los registros alternativos, sino entre la memoria y registros, y la pila (o memoria en general) y los registros HL, IX e IY.
 +
 +|< 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 puede ser 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 00h", al hacer el EX (SP), HL, el registro HL valdrá "00FFh".+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>
  
-Si queréis comprobarlopodéis hacerlo mediante el siguiente programa:+Ocon el siguiente código más rápido en ejecución:
  
 <code z80> <code z80>
- ; Ejemplo que muestra el intercambio de registros +push hl 
- ; mediante el uso de la pila (PUSH/POP). +ld l, c 
- ORG 40000+ld h, b 
 +pop bc 
 +</code>
  
- ; Cargamos en DE el valor 12345 y +En su momento veremos cómo funciona la pilapor ahora basta con saber que tenemos la posibilidad de intercambiar registros mediante el uso de la misma. Podríamos haber optado por no explicar este pequeño truco hasta haber hablado de la pila, pero nos parece más conveniente el hecho de tener toda la información sobre ensamblador agrupada de forma al buscar información o referencias sobre instrucciones para intercambiar valores de registrospueda encontrarse toda junta. Como hemos comentado al principio de este capítulo, resulta muy complicado explicar un lenguaje tan interrelacionado de forma que no se solapen diferentes áreas, de modo que la comprensión total de muchos de los conceptos se alcanzará con una segunda lectura del curso completo.
- ; realizamos un intercambio de valores +
- ; con BC, mediante la pila+
- LD DE12345 +
- LD BC0+
  
- PUSH DE +\\  
- PUSH BC +===== ¿Sirven de algo los Shadow Registers? =====
- POP DE +
- POP BC+
  
- ; Volvemosahora BC=DE y DE=BC +Como hemos vistocon 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. 
- RET+ 
 +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. 
 + 
 +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 BCDE 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 af, af<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? 
 + 
 +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): 
 + 
 +**Opción 1.- Usar la pila:** 
 + 
 +<code z80> 
 +    ld b, 8 
 +    push bc              ; Necesitamos salvaguardar BC 
 +    ld b, 9              ; (porque vamos a usarlo para algo) 
 + 
 +    ... hacer algo con BC ... 
 +     
 +    pop bc               ; recuperar el valor de BC
 </code> </code>
-Lo ensamblamos: 
  
-<code> +**Opción 2.- Usar la memoria:** 
- pasmo --tapbas cambio.asm cambio.tap+ 
 +<code z80
 +    ld b, 8 
 +    ld (1000), bc        ; Necesitamos salvaguardar BC 
 +    ld b,9               ; (porque vamos a usarlo para algo) 
 +     
 +    ..... 
 +     
 +    ld (1002), bc        ; guardamos el resultado 
 +    ld bc, (1000)        ; restauramos el valor de BC 
 +    ....
 </code> </code>
  
-Tras esto lo cargamos en un emulador de Spectrum (como un fichero TAP), nos vamos al BASIC y tecleamos "PRINT AT 10, 10; USR 40000"En pantalla aparecerá el valor "12345", ya que las rutinas llamadas desde BASIC devuelven sus resultados en BC, y nosotros hemos hecho un intercambio mediante la pila, entre DE y BC.+**Opción 3.- Usar "automodifying opcodes":**
  
-En su momento veremos cómo funciona la pila, por ahora basta con saber que tenemos la posibilidad de intercambiar registros mediante el uso de la mismaPodríamos haber optado por no explicar este pequeño truco hasta haber hablado de la pilapero nos parece más conveniente el hecho de tener toda la información sobre ensamblador agrupada de forma que cuando en el futuro (una vez terminado el curso completobusquéis información sobre instrucciones para intercambiar valores de registrospodáis encontrarla toda juntacomo un libro o una guía de referencia. Como hemos comentado al principio de esta entrega, resulta muy complicado explicar un lenguaje tan interrelacionado de forma que no se solapen diferentes áreas, de modo que la comprensión total de muchos de los conceptos se alcanzará con una segunda lectura del curso completo.+Esto es ligeramente más lento que usar PUSH y POP pero nos puede servir si no tenemos la pila disponible. En este caso, vamos a sobreescribir un instrucción de carga "futura" para poder recuperar el valor de un registro cuando se ejecute ese opcode modificado previamente: 
 + 
 +<code z80> 
 +    ld (save_bc+1), bc    ; Escribimos BC en la parte NN NN del 
 +                          ; opcode "ld bc, NN NN" en memoria 
 + 
 +    ... hacer cosas con BC, perdiendo su valor ... 
 + 
 +save_bc: 
 +    ld bc, $0000          ; En el LD anterior cambiamos $000 
 +                          ; por el valor de BCasí que cuando 
 +                          ; el z80 llegue aquí no es ya ld bc, 0 
 +                          ; sino ld bc, valor_que_tenia_BC 
 +                          ; así que recuperaremos BC aquí. 
 +</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**. 
 + 
 +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 estoestamos 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'').
  
 \\  \\ 
 ===== En resumen ===== ===== En resumen =====
  
-En esta entrega hemos visto la sintaxis de los programas en ensamblador (o, al menos, la sintaxis general de PASMO, el ensamblador que recomendamos), así como una descripción completa del juego de registros del Z80, incluyendo entre ellos el registro de flags F.+ Hemos visto la sintaxis de los programas en ensamblador (o, al menos, la sintaxis general de Pasmo, el ensamblador que recomendamos), así como una descripción completa del juego de registros del Z80, incluyendo entre ellos el registro de flags F.
  
 Además, hemos comenzado a ver nuestras primeras instrucciones del lenguaje ensamblador, en especial las instrucciones de carga, incremento y decremento, y aritméticas. Además, hemos comenzado a ver nuestras primeras instrucciones del lenguaje ensamblador, en especial las instrucciones de carga, incremento y decremento, y aritméticas.
  
-En la próxima entrega continuaremos detallando las diferentes instrucciones del Z80, ejemplos de uso y su efecto sobre los flags del registro F. Hasta entonces, os recomiendo la lectura del fichero "progcard.txt" adjunto con este artículo, donde encontraréis una gran referencia de instrucciones y flags.+En el próximo capítulo continuaremos detallando las diferentes instrucciones del Z80, ejemplos de uso y su efecto sobre los flags del registro F.
  
 \\  \\ 
 ===== 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}}
Línea 1062: Línea 1075:
     * [[http://www.worldofspectrum.org/faq/reference/Z80reference.htm|Z80 Reference de WOS]]     * [[http://www.worldofspectrum.org/faq/reference/Z80reference.htm|Z80 Reference de WOS]]
     * [[http://www.z80.info/lesson1.htm|Curso de ensamblador de z80.info]]     * [[http://www.z80.info/lesson1.htm|Curso de ensamblador de z80.info]]
-    * [[http://www.arrakis.es/~ninsesabe/pasmo/|Pasmo]] 
  
  
 +\\ 
 +**[ [[.:indice|⬉]] | [[.:arquitectura|⬅]] | [[.:lenguaje_2|➡]] ]**
  • cursos/ensamblador/lenguaje_1.1291917877.txt.gz
  • Última modificación: 09-12-2010 18:04
  • por sromero