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 [15-01-2024 08:24] – [Arquitectura del Z80 e Instrucciones básicas] sromerocursos:ensamblador:lenguaje_1 [22-01-2024 07:51] (actual) – [Instrucciones LD (instrucciones de carga)] sromero
Línea 12: Línea 12:
 ===== 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 23: Línea 23:
 Además, podemos agrupar algunos de estos registros en pares de 16 bits para determinadas operaciones: Además, podemos agrupar algunos de estos registros en pares de 16 bits para determinadas operaciones:
 \\  \\ 
-    * **AF**: Formado por el registro A como byte más significativo (Byte alto) y por F como byte menos significativo (Byte bajo). Si A vale $FF y F vale $00, AF valdrá automáticamente "$FF00".+    * **AF**: Formado por el registro A como byte más significativo (Byte alto) y por F como byte menos significativo (Byte bajo). Si ''A'' vale **$ff** ''F'' vale **$00**''AF'' valdrá automáticamente **$ff00**.
     * **BC**: Agrupación de los registros B y C que se puede utilizar en bucles y para acceder a puertos. También se utiliza como "repetidor" o "contador" en las operaciones de acceso a memoria (''LDIR'', ''LDDR'', etc.).     * **BC**: Agrupación de los registros B y C que se puede utilizar en bucles y para acceder a puertos. También se utiliza como "repetidor" o "contador" en las operaciones de acceso a memoria (''LDIR'', ''LDDR'', etc.).
     * **DE, HL**: Registros de 16 bits formados por D y E por un lado y H y L por otro. Utilizaremos generalmente estos registros para leer y escribir en memoria en una operación única, así como para las operaciones de acceso a memoria como ''LDIR'', ''LDDR'', etc.     * **DE, HL**: Registros de 16 bits formados por D y E por un lado y H y L por otro. Utilizaremos generalmente estos registros para leer y escribir en memoria en una operación única, así como para las operaciones de acceso a memoria como ''LDIR'', ''LDDR'', etc.
Línea 35: Línea 35:
  Por último, tenemos disponible un banco alternativo de registros, conocidos como **Shadow Registers** o //Registros Alternativos//, que se llaman igual que sus equivalentes principales pero con una comilla simple detrás: A', F', B', C', D'. E', H' y L'.  Por último, tenemos disponible un banco alternativo de registros, conocidos como **Shadow Registers** o //Registros Alternativos//, que se llaman igual que sus equivalentes principales pero con una comilla simple detrás: A', F', B', C', D'. E', H' y L'.
  
-En cualquier momento podemos intercambiar el valor de los registros A, B, C, D, E, F, H y L con el valor de los registros A', B', C', D', E', F', H' y L' mediante las instrucciones de ensamblador ''EX AF, AF<nowiki>'</nowiki>'' y ''EXX''. La utilidad de estos Shadow Registers es almacenar valores temporales y proporcionarnos más registros para operar: podremos intercambiar el valor de los registros actuales con los temporales, realizar operaciones con los registros sin perder los valores originales (que al hacer el intercambio se quedarán en los registros Shadow), y después recuperar los valores originales volviendo a ejecutar un intercambio.+En cualquier momento podemos intercambiar el valor de los registros A, B, C, D, E, F, H y L con el valor de los registros A', B', C', D', E', F', H' y L' mediante las instrucciones de ensamblador ''EX AF, AF<nowiki>'</nowiki>'' y ''exx''. La utilidad de estos Shadow Registers es almacenar valores temporales y proporcionarnos más registros para operar: podremos intercambiar el valor de los registros actuales con los temporales, realizar operaciones con los registros sin perder los valores originales (que al hacer el intercambio se quedarán en los registros Shadow), y después recuperar los valores originales volviendo a ejecutar un intercambio.
  
 Ya conocemos los registros disponibles, veamos ahora ejemplos de operaciones típicas que podemos realizar con ellos: Ya conocemos los registros disponibles, veamos ahora ejemplos de operaciones típicas que podemos realizar con ellos:
Línea 47: Línea 47:
  
 <code z80> <code z80>
-    LD C, $00       ; C vale 0 +    ld c, $00            ; C vale 0 
-    LD B, $01       ; B vale 1 +    ld b, $01            ; B vale 1 
-                    ; con esto, BC = $0100 +                         ; con esto, BC = $0100 
-    LD AB         ; A ahora vale 1 +    ld ab              ; A ahora vale 1 
-    LD HL, $1234    ; HL vale $1234 o 4660d +    ld hl, $1234         ; HL vale $1234 o 4660d 
-    LD A, (HL     ; A contiene el valor de (4660) +    ld a, (hl          ; A contiene el valor de (4660) 
-    LD A, (16384)   ; A contiene el valor de (16384) +    ld a, (16384)        ; A contiene el valor de (16384) 
-    LD (16385), A   ; Escribimos en (16385) el valor de A +    ld (16385), a        ; Escribimos en (16385) el valor de A 
-    ADD AB        ; Suma: A = A + B +    add ab             ; Suma: A = A + B 
-    INC B           ; Incrementamos B (B = 1+1 =2) +    inc b                ; Incrementamos B (B = 1+1 =2) 
-                    ; Ahora BC vale $0200 +                         ; Ahora BC vale $0200 
-    INC  BC         ; Incrementamos BC +    inc  bc              ; Incrementamos BC 
-                    ; (BC = $0200+1 = $0201)+                         ; (BC = $0200+1 = $0201)
 </code> </code>
  
- Dentro del ejemplo anterior queremos destacar el operador "()", que significa "el contenido de la memoria apuntado por". Así, ''LD A, (16384)'' no quiere decir "mete en A el valor 16384" (cosa que además no se puede hacer porque A es un registro de 8 bits), sino "mete en A el valor de 8 bits que contiene la celdilla de memoria 16384" (equivalente a utilizar en BASIC las funciones ''PEEK'' y ''POKE'', como en ''LET A=PEEK 16384'').+ Dentro del ejemplo anterior queremos destacar el operador "()", que significa "el contenido de la memoria apuntado por". Así, ''ld a, (16384)'' no quiere decir "mete en A el valor 16384" (cosa que además no se puede hacer porque A es un registro de 8 bits), sino "mete en A el valor de 8 bits que contiene la celdilla de memoria 16384" (equivalente a utilizar en BASIC las funciones ''PEEK'' y ''POKE'', como en ''LET A=PEEK 16384'').
  
 Cabe destacar un gran inconveniente del juego de instrucciones del Z80, y es que no es //ortogonal//. Se dice que el juego de instrucciones de un microprocesador es ortogonal cuando puedes realizar todas las operaciones sobre todos los registros, sin presentar excepciones. En el caso del Z80 no es así, ya que hay determinadas operaciones que podremos realizar sobre unos registros pero no sobre otros. Cabe destacar un gran inconveniente del juego de instrucciones del Z80, y es que no es //ortogonal//. Se dice que el juego de instrucciones de un microprocesador es ortogonal cuando puedes realizar todas las operaciones sobre todos los registros, sin presentar excepciones. En el caso del Z80 no es así, ya que hay determinadas operaciones que podremos realizar sobre unos registros pero no sobre otros.
Línea 69: Línea 69:
  
 <code z80> <code z80>
-    LD BC, $1234 +    ld bc, $1234 
-    LD HLBC +    ld hlbc 
-    LD SPBC +    ld spbc 
-    EX DEHL +    ex dehl 
-    EX BCDE +    ex bcde 
-    ADD HLBC +    add hlbc 
-    ADD DEBC+    add debc
 </code> </code>
  
Línea 81: Línea 81:
  
 <code z80> <code z80>
-   LD SPBC      ; NO: No se puede cargar el valor un registro en SP,+   ld spbc      ; NO: No se puede cargar el valor un registro en SP,
                   ; sólo se puede cargar un valor inmediato NN                   ; sólo se puede cargar un valor inmediato NN
  
-   EX BCDE      ; NO: Existe EX DEHL, pero no EX BC, DE+   ex bcde      ; NO: Existe ex dehl, pero no EX BC, DE
  
-   ADD DEBC     ; NO: Sólo se puede usar HL como operando destino+   add debc     ; NO: Sólo se puede usar HL como operando destino
                   ; en las sumas de 16 bytes con registros de propósito                   ; en las sumas de 16 bytes con registros de propósito
                   ; general. Una alternativa sería:                   ; general. Una alternativa sería:
                   ;                   ;
-                  ; LD HL, 0        ; HL = 0 +                  ; ld hl, 0        ; HL = 0 
-                  ; ADD HLBC      ; HL = HL + BC +                  ; add hlbc      ; HL = HL + BC 
-                  ; EX DEHL       ; Intercambiamos el valor de HL y DE+                  ; ex dehl       ; Intercambiamos el valor de HL y DE
  
-   LD BCDE      ; NO:, pero se pueden tomar alternativas, como por ej:+   ld bcde      ; NO:, pero se pueden tomar alternativas, como por ej:
                   ;                   ;
-                  ; PUSH DE +                  ; push de 
-                  ; POP BC+                  ; pop bc
                   :                   :
                   ; o también:                   ; o también:
                   ;                   ;
-                  ; LD BD +                  ; ld bd 
-                  ; LD CE+                  ; ld ce
  
-   LD DEHL      ; NO: mismo caso anterior.+   ld dehl      ; NO: mismo caso anterior.
  
-   LD SPBC      ; NO: no existe como instrucción.+   ld spbc      ; NO: no existe como instrucción.
 </code> </code>
  
Línea 124: Línea 124:
 Debido a esto, no debemos modificar el registro IY, a menos que cumplamos estas 2 condiciones: Debido a esto, no debemos modificar el registro IY, a menos que cumplamos estas 2 condiciones:
  
-1.- Cambiamos el modo de funcionamiento del Spectrum de IM1 (su modo por defecto) a IM2 (un modo "personalizado"). +  - Cambiamos el modo de funcionamiento del Spectrum de im1 (su modo por defecto) a im2 (un modo "personalizado"). 
-2.- No llamamos ni utilizamos ninguna rutina de la ROM para ninguna tarea.+  - No llamamos ni utilizamos ninguna rutina de la ROM para ninguna tarea.
  
 Ambas cosas son el procedimiento habitual en un juego o programa, por lo que podremos utilizar el registro IY en nuestros programas, pero no inicialmente. A lo largo del curso no lo usaremos ya que vamos a utilizar rutinas de la ROM de apoyo para la mayoría de nuestros ejemplos. Ambas cosas son el procedimiento habitual en un juego o programa, por lo que podremos utilizar el registro IY en nuestros programas, pero no inicialmente. A lo largo del curso no lo usaremos ya que vamos a utilizar rutinas de la ROM de apoyo para la mayoría de nuestros ejemplos.
Línea 152: Línea 152:
 <code z80> <code z80>
             ; Repetir algo 100 veces             ; Repetir algo 100 veces
-            LD B, 100+            ld b, 100
     bucle:     bucle:
-            (...)        ; código+            (...)              ; código
  
-            DEC B        ; Decrementamos B (B=B-1) +            dec b              ; Decrementamos B (B=B-1) 
-            JR NZ, bucle+            jr nz, bucle 
 +            
             ; Si el resultado de la operación anterior no es cero (NZ = Non Zero),             ; Si el resultado de la operación anterior no es cero (NZ = Non Zero),
-            ; saltar a la etiqueta bucle y continuar. DEC B hará que el flag Z +            ; saltar a la etiqueta bucle y continuar. dec b hará que el flag Z 
-            ; se ponga a 1 cuando B llegue a cero, lo que afectará al JR NZ.+            ; se ponga a 1 cuando B llegue a cero, lo que afectará al jr NZ.
             ; Como resultado, este trozo de código (...) se ejecutará 100 veces.             ; Como resultado, este trozo de código (...) se ejecutará 100 veces.
  
 </code> </code>
  
- Como veremos en su momento, existe una instrucción equivalente a ''DEC B'' + ''JR NZ'' que es más cómoda de utilizar y más rápida que estas 2 instrucciones juntas (''DJNZ''), pero se ha elegido el ejemplo que tenéis arriba para que veáis cómo muchas operaciones (en este caso DEC) afectan a los flags, y la utilidad que estos tienen a la hora de programar.+ Como veremos en su momento, existe una instrucción equivalente a ''dec b'' + ''JR NZ'' que es más cómoda de utilizar y más rápida que estas 2 instrucciones juntas (''DJNZ''), pero se ha elegido el ejemplo que tenéis arriba para que veáis cómo muchas operaciones (en este caso DEC) afectan a los flags, y la utilidad que estos tienen a la hora de programar.
  
 Además de para bucles, también podemos utilizarlo para comparaciones. Supongamos que queremos hacer en ensamblador una comparación de igualdad, algo como: Además de para bucles, también podemos utilizarlo para comparaciones. Supongamos que queremos hacer en ensamblador una comparación de igualdad, algo como:
Línea 177: 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 224: Línea 225:
  
 <code z80> <code z80>
-LD DESTINO, ORIGEN+ld DESTINO, ORIGEN
 </code> </code>
  
Línea 232: Línea 233:
  
 <code z80> <code z80>
-LD A, 10         ; A = 10 +ld a, 10              ; A = 10 
-LD B, 200        ; B = 200 +ld b, 200             ; B = 200 
-LD BC, 12345     ; BC = 12345+ld bc, 12345          ; BC = 12345
 </code> </code>
  
Línea 240: Línea 241:
  
 <code z80> <code z80>
-LD AB          ; A = B +ld ab               ; A = B 
-LD BCDE        ; BC = DE+ld bcde             ; BC = DE
 </code> </code>
  
Línea 247: Línea 248:
  
 <code z80> <code z80>
-LD (12345), A    ; Memoria[12345] = valor en A +ld (12345), a         ; Memoria[12345] = valor en A 
-LD (HL), 10      ; Memoria[valor de HL] = 10+ld (hl), 10           ; Memoria[valor de HL] = 10
 </code> </code>
  
Línea 254: Línea 255:
  
 <code z80> <code z80>
-LD A, (12345)    ; A = valor en Memoria[12345] +ld a, (12345)         ; A = valor en Memoria[12345] 
-LD B, (HL      ; B = valor en Memoria[valor de HL]+ld b, (hl           ; B = valor en Memoria[valor de HL]
 </code> </code>
  
- Nótese cómo el operador () nos permite acceder a memoria. En nuestros ejemplos, ''LD A, (12345)'' no significa meter en A el valor 12345 (cosa imposible al ser un registro de 16 bits) sino almacenar en el registro A el valor que hay almacenado en la celdilla número 12345 de la memoria del Spectrum.+ Nótese cómo **el operador ''()'' nos permite acceder a la memoria del Spectrum**. En nuestros ejemplos, ''ld a, (12345)'' no significa meter en A el valor 12345 (cosa imposible al ser un registro de 16 bits) sino almacenar en el registro A el valor que hay almacenado en la celdilla número 12345 de la memoria del Spectrum. 
 + 
 + Este operador indica que se hace referencia a una posición de memoria referenciada por el valor que hay dentro de los paréntesis. Dicho valor referencia a una celdilla de memoria de 8 bits. 
 + 
 + Es decir, si escribiéramos en BASIC del Spectrum (con ''PEEK'' y ''POKE'') las instrucciones de carga de 8 bits que referencian a la memoria, veríamos lo siguiente: 
 + 
 +<code z80> 
 +ld a, (16384)     =>    LET A = PEEK 16384 
 +ld (16384), a     =>    POKE 16384, a 
 + 
 +ld hl, 16384      =>    HL = 16384 
 +ld a, (hl)        =>    LET A = PEEK HL  =>    LET A = PEEK 16384 
 +ld (hl), a        =>    POKE HL, a       =>    POKE (16384), a 
 +</code> 
 + 
 +En el segundo ejemplo hemos utilizado ''ld hl, 16384'', que significa "carga en HL el valor 16384". Como no hay paréntesis en la instrucción, no estamos haciendo una referencia a memoria sino al valor inmediato 16384, el cual metemos en HL. Después, al utilizar los paréntesis en ''ld a, (hl)'', sí que hacemos una referencia a memoria, con la dirección absoluta contenida en HL. 
 + 
 +No sólo podemos leer de o escribir en una dirección de memoria valores de 8 bits, también podemos leer y escribir valores de 16 bits. Evidentemente, como la memoria es un conjunto de "celdillas de 8 bits", para leer o escribir valores de 16 bits lo haremos en 2 celdillas: la celdilla apuntada por la dirección, y la siguiente. 
 + 
 +De nuevo, viéndolo "en instrucciones BASIC", podemos ver la diferencia entre asignar un valor de 16 bits inmediato, o referenciar a una posición de memoria para leer 16 bits: 
 + 
 +<code z80> 
 +ld hl, 16384      =>    HL = 16384 
 + 
 +ld hl, (16384)    =>    HL = (PEEK 16384) + 256*(PEEK 16385) 
 +                           => L = PEEK 16384 
 +                              H = PEEK 16385 
 +</code> 
 + 
 +En ''ld hl, (16384)'', metemos en HL el dato de 16 bits en 16384 y 16385, un valor de 8 bits para cada uno de los 2 registrow de 8 bits de HL (concretamente, H será el valor contenido en 16385 y L el valor en 16384, posteriormente veremos por qué se leen en orden inverso). 
 + 
 +De la misma forma, si hablamos de escribir en memoria un valor de 16 bits: 
 + 
 +<code z80> 
 +ld (16384), hl    =>    POKE 16384, L 
 +                        POKE 16385, H 
 +</code>
  
 En un microprocesador con un juego de instrucciones ortogonal, se podría usar cualquier origen y cualquier destino sin distinción. En el caso del Z80 no es así. El listado completo de operaciones válidas con LD es el siguiente: En un microprocesador con un juego de instrucciones ortogonal, se podría usar cualquier origen y cualquier destino sin distinción. En el caso del Z80 no es así. El listado completo de operaciones válidas con LD es el siguiente:
Línea 277: Línea 314:
 <code z80> <code z80>
 ; Carga de valores en registros ; Carga de valores en registros
-LD r, N +ld r, N 
-LD rr, NN +ld rr, NN 
-LD ri, NN+ld ri, NN
  
 ; Copia de un registro a otro ; Copia de un registro a otro
-LD r, r +ld r, r 
-LD rr, rr+ld rr, rr
  
 ; Acceso a memoria ; Acceso a memoria
-LD r, (HL+ld r, (hl
-LD (NN), A +ld (NN), a 
-LD (HL), N +ld (hl), N 
-LD A, (rr)      ; (excepto rr=SP) +ld a, (rr)      ; (excepto rr=SP) 
-LD (rr),      ; (excepto rr=SP) +ld (rr),      ; (excepto rr=SP) 
-LD A, (NN) +ld a, (NN) 
-LD rr, (NN) +ld rr, (NN) 
-LD ri, (NN) +ld ri, (NN) 
-LD (NN), rr +ld (NN), rr 
-LD (NN), ri+ld (NN), ri
  
 ; Acceso indexado a memoria ; Acceso indexado a memoria
-LD (ri+N), r +ld (ri+N), r 
-LD r, (ri+N) +ld r, (ri+N) 
-LD (ri+N), N+ld (ri+N), N
 </code> </code>
  
Línea 307: Línea 344:
 <code z80> <code z80>
 ; Manipulación del puntero de pila (SP) ; Manipulación del puntero de pila (SP)
-LD SP, ri +ld sp, ri 
-LD SPHL+ld sphl
  
 ; Para manipular el registro I ; Para manipular el registro I
-LD AI +ld ai 
-LD IA+ld ia
  
 ; Para manipular el registro R ; Para manipular el registro R
-LD AR +ld ar 
-LD RA+ld ra
 </code> </code>
  
Línea 324: Línea 361:
 ; Carga de valores en registros ; Carga de valores en registros
 ; registro_destino = valor ; registro_destino = valor
-LD A, 100          LD r, N +ld a, 100               ld r, N 
-LD BC, 12345       LD rr, NN+ld bc, 12345            ld rr, NN
  
 ; Copia de registros en registros ; Copia de registros en registros
 ; registro_destino = registro_origen ; registro_destino = registro_origen
-LD BC            LD r, r +ld bc                 ld r, r 
-LD AB            LD r, r +ld ab                 ld r, r 
-LD BCDE          LD rr, rr+ld bcde               ld rr, rr
  
 ; Acceso a memoria ; Acceso a memoria
 ; (Posicion_memoria) = VALOR o bien ; (Posicion_memoria) = VALOR o bien
 ;  Registro = VALOR en (Posicion de memoria) ;  Registro = VALOR en (Posicion de memoria)
-LD A, (HL        LD r, (rr) +ld a, (hl             ld r, (rr) 
-LD (BL), B         LD (rr), r +ld (bc), a              ld (rr), r 
-LD (12345), A      LD (NN), A +ld (12345), a           ld (NN), a 
-LD A, (HL        LD r, (rr) +ld a, (hl             ld r, (rr) 
-LD (DE), A         LD (rr), r +ld (de), a              ld (rr), r 
-LD (BC), 1234h     LD (BC), NN +ld (bc), 1234h          ld (bc), NN 
-LD (12345), DE     LD (NN), rr +ld (12345), de          ld (NN), rr 
-LD IX, (12345)     ; LD ri, (NN) +ld ix, (12345)          ; LD ri, (NN) 
-LD (34567), IY     LD (NN), ri+ld (34567), iy          ld (NN), ri
  
 ; Acceso indexado a memoria ; Acceso indexado a memoria
 ; (Posicion_memoria) = VALOR o VALOR = (Posicion_memoria) ; (Posicion_memoria) = VALOR o VALOR = (Posicion_memoria)
 ; Donde la posicion es IX+N o IY+N: ; Donde la posicion es IX+N o IY+N:
-LD (IX+10), A      ; LD (ri+N), r +ld (ix+10), a           ; LD (ri+N), r 
-LD A, (IY+100)     LD r, (ri+N) +ld a, (iy+100)          ld r, (ri+N) 
-LD (IX-30), 100    ; LD (ri+N), N+ld (ix-30), 100         ; LD (ri+N), N
 </code> </code>
  
  Hagamos hincapié de nuevo en el mismo detalle: debido a que el juego de instrucciones del Z80 no es ortogonal, en ocasiones no podemos ejecutar ciertas operaciones que podrían sernos útiles con determinados registros. En ese caso tendremos que buscar una solución mediante los registros y operaciones válidas de que disponemos.  Hagamos hincapié de nuevo en el mismo detalle: debido a que el juego de instrucciones del Z80 no es ortogonal, en ocasiones no podemos ejecutar ciertas operaciones que podrían sernos útiles con determinados registros. En ese caso tendremos que buscar una solución mediante los registros y operaciones válidas de que disponemos.
  
- Un detalle muy importante respecto a las instrucciones de carga: **en el caso de las operaciones LD, el registro F no ve afectado ninguno de sus indicadores o flags en relación al resultado de la ejecución de las mismas** (salvo en el caso de ''LD AI'' y ''LD AR'').+ Un detalle muy importante respecto a las instrucciones de carga: **en el caso de las operaciones LD, el registro F no ve afectado ninguno de sus indicadores o flags en relación al resultado de la ejecución de las mismas** (salvo en el caso de ''ld ai'' y ''ld ar'').
  
 <code> <code>
                          Flags                          Flags
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
- ---------------------------------- + --------------------------------- 
-   LD r, r           |- - - - - -| +   ld r, r           |- - - - - -| 
-   LD r, N           |- - - - - -| +   ld r, N           |- - - - - -| 
-   LD rr, rr         |- - - - - -| +   ld rr, rr         |- - - - - -| 
-   LD (rr),        |- - - - - -| +   ld (rr),        |- - - - - -| 
-   LD (rr),        |- - - - - -| +   ld (rr),        |- - - - - -| 
-   LD ri, (NN)       |- - - - - -| +   ld ri, (NN)       |- - - - - -| 
-   LD (NN), ri       |- - - - - -| +   ld (NN), ri       |- - - - - -| 
-   LD (ri+d), N      |- - - - - -| +   ld (ri+d), N      |- - - - - -| 
-   LD (ri+d), r      |- - - - - -| +   ld (ri+d), r      |- - - - - -| 
-   LD r, (ri+d)      |- - - - - -| +   ld r, (ri+d)      |- - - - - -| 
-   LD A          |* * 0 * 1 0| +   ld a          |* * 0 * 1 0| 
-   LD A          |* * 0 * 1 0|+   ld a          |* * 0 * 1 0|
 </code> </code>
 \\  \\ 
  
-Esto quiere decir, y es muy importante, que una operación como ''LD A, 0'', por ejemplo, no activará el flag de Zero del registro F.+Esto quiere decir, y es muy importante, que una operación como ''ld a, 0'', por ejemplo, no activará el flag de Zero del registro F.
  
-Al respecto de escritura y lectura de valores de 16 bits utilizando instrucciones que trabajan con 8 bits, queremos recordar en este punto que el Z80 es una CPU LITTLE-ENDIAN por lo que los valores de 16 bits aparecerán en memoria "invertidos", es decir, primero el byte menos significativo y en la celdilla siguiente el byte más significativo.+\\  
 +===== Tamaños y ciclos ===== 
 + 
 +Hay otros dos datos que, como la afectación de flags, son muy importantes sobre las diferentes instrucciones que iremos viendo. 
 + 
 +Uno es el tamaño en bytes de cada instrucción, que viene determinado por los opcodes que ocupa. Así, un ''ld a, b'' ocupa un sólo byte (**$78**), ''ld a, $ff'' ocupa 2 bytes (al opcode **$3E** le sigue el operando **$ff**) y ''ld bc, $1234'' ocupa 3 bytes (al opcode **01** le siguen los 2 bytes de $1234). 
 + 
 +Del mismo modo, tenemos el tiempo que tarda en ejecutarse cada una de ellas, lo que se conoce como el número de **ciclos** (o **t-estados** / **t-states**). Este es el tiempo que tarda el procesador Z80 en leer de memoria, decodificar y ejecutar cada instrucción. Cada lectura de byte de memoria requiere en general de 3 t-estados extra, así que no lo es lo mismo una instrucción sencilla de un sólo byte de opcode como ''ld a, b'' que una con 3 bytes como ''ld bc, $1234''
 + 
 +En nuestro ejemplo anterior, el ''ld a, b'' de un sólo byte se ejecuta en 4 ciclos de reloj (3 para leer de memoria el opcode y 1 para ejecutarlo), el ''ld a, $ff'' son 7 ciclos de reloj o t-estados (los 3 de leer de memoria el primer byte, 3 de leer el segundo byte, y uno más para la ejecución) y ''ld bc, $1234'' que ocupa 3 bytes requiere 3+3+3+1 = 10 ciclos de reloj. 
 + 
 +En estos capítulos iniciales del curso no nos deben de preocupar tanto lo que ocupan las instrucciones y cuánto tardan en ejecutarse como el saber qué instrucciones existen, la manera en que operan y cómo se utilizan. No obstante, sí que necesitaremos más adelante tener un buen conocimiento de tamaño y tiempo de ejecución de cada instrucción para desarrollar programas. 
 + 
 +En el último capítulo dedicado a las diferentes instrucciones veremos una tabla donde se detallan todos los tamaños y tiempos de las diferentes instrucciones. 
 + 
 +Un apunte sobre ''ld bc, $1234'': Al respecto de escritura y lectura de valores de 16 bits utilizando instrucciones que trabajan con 8 bits, queremos recordar en este punto que el Z80 es una CPU LITTLE-ENDIAN por lo que los valores de 16 bits aparecerán en memoria "invertidos", es decir, primero el byte menos significativo y en la celdilla siguiente el byte más significativo. Es decir, que el opcode correspondiente a ''ld bc, $1234'' en memoria no es "**$01 $12 $34**" sino "**$01 $34 $12**";
  
 \\  \\ 
Línea 389: Línea 441:
  
 <code z80> <code z80>
-LD A, 0      ; A = 0 +ld a, 0                ; A = 0 
-INC A        ; A = A+1 = 1 +inc a                  ; A = A+1 = 1 
-LD BA      ; B = A = 1 +ld ba                ; B = A = 1 
-INC B        ; B = B+1 = 2 +inc b                  ; B = B+1 = 2 
-INC B        ; B = B+1 = 3 +inc b                  ; B = B+1 = 3 
-LD  BC, 0 +ld  bc, 0 
-INC BC       ; BC = 0001h +inc bc                 ; BC = $0001 
-INC B        ; BC = 0101h (ya que B=B+1 y es la parte alta) +inc b                  ; BC = $0101 (ya que B=B+1 y es la parte alta) 
-DEC A        ; A = A-1 = 0+dec a                  ; A = A-1 = 0
 </code> </code>
  
Línea 403: Línea 455:
  
 <code z80> <code z80>
-INC +inc 
-DEC +dec 
-INC rr +inc rr 
-DEC rr+dec rr
 </code> </code>
  
Línea 412: Línea 464:
  
 <code z80> <code z80>
-INC (HL+inc (hl
-DEC (HL)+dec (hl)
 </code> </code>
  
Línea 419: 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 430: Línea 482:
  
 <code z80> <code z80>
-INC A          ; A = A+1 +inc a               ; A = A+1 
-DEC B          ; B = B-1 +dec b               ; B = B-1 
-INC DE         ; DE = DE+1 +inc de              ; DE = DE+1 
-DEC IX         ; IX = IX-1 +dec ix              ; IX = IX-1 
-INC (HL      ; (HL) = (HL)+1 +inc (hl           ; (HL) = (HL)+1 
-INC (IX-5)     ; (IX-5) = (IX-5)+1 +inc (ix-5)          ; (IX-5) = (IX-5)+1 
-DEC (IY+100)   ; (IY+100) = (IY+100)+1+dec (iy+100)        ; (IY+100) = (IY+100)+1
 </code> </code>
  
 Unos apuntes sobre la afectación de los flags ante el uso de INC y DEC: Unos apuntes sobre la afectación de los flags ante el uso de INC y DEC:
  
-    * Si un registro de 8 bits vale 255 ($FF) y lo incrementamos, pasará a valer 0. +    * Si un registro de 8 bits vale 255 ($ff) y lo incrementamos, pasará a valer 0. 
-    * Si un registro de 16 bits vale 65535 ($FFFF) y lo incrementamos, pasará a valer 0. +    * Si un registro de 16 bits vale 65535 ($ffff) y lo incrementamos, pasará a valer 0. 
-    * Si un registro de 8 bits vale 0 y lo decrementamos, pasará a valer 255 ($FF). +    * Si un registro de 8 bits vale 0 y lo decrementamos, pasará a valer 255 ($ff). 
-    * Si un registro de 16 bits vale 0 ($0) y lo decrementamos, pasará a valer 65535 ($FF).+    * Si un registro de 16 bits vale 0 ($0) y lo decrementamos, pasará a valer 65535 ($ff).
     * En estos desbordamientos no se tomará en cuenta para nada el bit de Carry (acarreo) de los flags (registro F), ni tampoco lo afectarán tras ejecutarse.     * En estos desbordamientos no se tomará en cuenta para nada el bit de Carry (acarreo) de los flags (registro F), ni tampoco lo afectarán tras ejecutarse.
-    * Las operaciones INC y DEC sobre registros de 16 bits (BC, DE, HL, IX, IY, SP) no afectan a los flags. Esto implica que no podemos usar como condición de flag zero para un salto el resultado de instrucciones como "DEC BC", por ejemplo.+    * Las operaciones INC y DEC sobre registros de 16 bits (BC, DE, HL, IX, IY, SP) no afectan a los flags. Esto implica que no podemos usar como condición de flag zero para un salto el resultado de instrucciones como "dec bc", por ejemplo.
     * Las operaciones INC y DEC sobre registros de 8 bits y sobre la memoria no afectan al flag de acarreo, pero sí que pueden afectar al flag de Zero (Z), al de Paridad/Overflow (P/V), al de Signo (S) y al de Half-Carry (H).     * Las operaciones INC y DEC sobre registros de 8 bits y sobre la memoria no afectan al flag de acarreo, pero sí que pueden afectar al flag de Zero (Z), al de Paridad/Overflow (P/V), al de Signo (S) y al de Half-Carry (H).
  
Línea 455: 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 494: Línea 546:
  
 <code z80> <code z80>
-ADD DESTINO, ORIGEN+add DESTINO, ORIGEN
 </code> </code>
  
Línea 500: 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 522: 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 542: Línea 594:
  
 <code z80> <code z80>
-ADD BC      ; Sólo A puede ser destino +add bc             ; Sólo A puede ser destino 
-ADD BCDE    ; Sólo puede ser destino HL +add bcde           ; Sólo puede ser destino HL 
-ADD IXIX    ; No podemos sumar un registro índice a él mismo+add ixix           ; No podemos sumar un registro índice a él mismo
 </code> </code>
  
 La afectación de los flags ante las operaciones de sumas es la siguiente: La afectación de los flags ante las operaciones de sumas es la siguiente:
  
-   * Para ''ADD A, s'', el registro N (Substraction) se pone a 0 (lógicamente, ya que sólo se pone a uno cuando se ha realizado una resta). El registro P/V se comporta como un registro de Overflow e indica si ha habido overflow (desbordamiento) en la operación. El resto de flags (Sign, Zero, Half-Carry y Carry) se verán afectados de acuerdo al resultado de la operación de suma.+   * Para ''add a, s'', el registro N (Substraction) se pone a 0 (lógicamente, ya que sólo se pone a uno cuando se ha realizado una resta). El registro P/V se comporta como un registro de Overflow e indica si ha habido overflow (desbordamiento) en la operación. El resto de flags (Sign, Zero, Half-Carry y Carry) se verán afectados de acuerdo al resultado de la operación de suma.
  
-   * Para ''ADD HL, ss'' y ''ADD ri, rr'', se pone a 0 el flag N, y sólo se verá afectado el flag de acarreo (C) de acuerdo al resultado de la operación.+   * Para ''add hl, ss'' y ''add ri, rr'', se pone a 0 el flag N, y sólo se verá afectado el flag de acarreo (C) de acuerdo al resultado de la operación.
  
 O, en forma de tabla de afectación: O, en forma de tabla de afectación:
Línea 559: 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 590: 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 600: Línea 652:
 ==== Resta: SUB (Substract) ==== ==== Resta: SUB (Substract) ====
  
-En el caso de las restas, sólo es posible realizar (de nuevo gracias a la no ortogonalidad del J.I. del Z80) la operación "A=A-origen", donde "origen" puede ser cualquier registro de 8 bits, valor inmediato de 8 bits, contenido de la memoria apuntada por [HL], o contenido de la memoria apuntada por un registro índice más un desplazamiento. El formato de la instrucción ''SUB'' no requiere 2 operandos, ya que el registro destino sólo puede ser A:+En el caso de las restas, sólo es posible realizar (de nuevo gracias a la no ortogonalidad del J.I. del Z80) la operación "A=A-origen", donde "origen" puede ser cualquier registro de 8 bits, valor inmediato de 8 bits, contenido de la memoria apuntada por (HL), o contenido de la memoria apuntada por un registro índice más un desplazamiento. El formato de la instrucción ''SUB'' no requiere 2 operandos, ya que el registro destino sólo puede ser A:
  
 <code z80> <code z80>
-SUB ORIGEN+sub ORIGEN
 </code> </code>
  
Línea 609: 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 618: Línea 670:
  
 <code z80> <code z80>
-SUB B           ; A = A - B +sub b           ; A = A - B 
-SUB 100         ; A = A - 100 +sub 100         ; A = A - 100 
-SUB [HL]        ; A = A - [HL] +sub (hl)        ; A = A - (HL) 
-SUB [IX+10    ; A = A - [IX+10]+sub (ix+10    ; A = A - (IX+10)
 </code> </code>
  
Línea 642: Línea 694:
  
 <code> <code>
-"ADC A, s"    equivale a    "A = A + s + CarryFlag" +"adc a, s"    equivale a    "A = A + s + CarryFlag" 
-"ADC HL, ss"  equivale a    "HL = HL + ss + CarryFlag"+"adc hl, ss"  equivale a    "HL = HL + ss + CarryFlag"
 </code> </code>
  
Línea 654: Línea 706:
   Instrucción       |S Z H P N C|   Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- ADC A,s            |* * * V 0 *| + adc a,s            |* * * V 0 *| 
- ADC HL,ss          |* * ? V 0 *|+ adc hl,ss          |* * ? V 0 *|
 </code> </code>
  
Línea 667: Línea 719:
  
 <code> <code>
-"SBC A, s"    equivale a    "A = A - s - CarryFlag" +"sbc a, s"    equivale a    "A = A - s - CarryFlag" 
-"SBC HL, ss"  equivale a    "HL = HL - ss - CarryFlag"+"sbc hl, ss"  equivale a    "HL = HL - ss - CarryFlag"
 </code> </code>
  
Línea 677: 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 796: Línea 848:
 Como ya se ha explicado, disponemos de un banco de registros alternativos (los Shadow Registers), y podemos conmutar los valores entre los registros estándar y los alternativos mediante unas determinadas instrucciones del Z80. Como ya se ha explicado, disponemos de un banco de registros alternativos (los Shadow Registers), y podemos conmutar los valores entre los registros estándar y los alternativos mediante unas determinadas instrucciones del Z80.
  
-El Z80 nos proporciona una serie de registros de propósito general (así como un registro de flags), de nombres A, B, C, D, E, F, H y L. El micro dispone también de unos registros extra (set alternativo conocido como Shadow Registers) de nombre A', B', C', D', E', F', H' y L', que aprovecharemos en cualquier momento de nuestro programa. No obstante, no podremos hacer uso directo de estos registros en instrucciones en ensamblador. No es posible, por ejemplo, ninguna de las siguientes instrucciones:+El Z80 nos proporciona una serie de registros de propósito general (así como un registro de flags), de nombres A, B, C, D, E, F, H y L. El micro dispone también de unos registros extra (set alternativo conocido como Shadow Registers) de nombre A', B', C', D', E', F', H' y L', que aprovecharemos en cualquier momento de nuestro programa. No obstante, no podremos hacer uso directo de estos registros en instrucciones en ensamblador. **No es posible**, por ejemplo, usar ninguna de las siguientes instrucciones (porque no existen):
  
 <code> <code>
-LD B', $10 +ld b', $10 
-INC A+inc a
-LD HL', $1234 +ld hl', $1234 
-LD A', ($1234)+ld a', ($1234)
 </code> </code>
  
Línea 809: Línea 861:
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| B | $A0 | B' | $00 |+| B | $a0 | B' | $00 |
 | C | $55 | C' | $00 | | C | $55 | C' | $00 |
 | D | $01 | D' | $00 | | D | $01 | D' | $00 |
-| E | $FF | E' | $00 |+| E | $ff | E' | $00 |
 | H | $00 | H' | $00 | | H | $00 | H' | $00 |
 | L | $31 | L' | $00 | | L | $31 | L' | $00 |
Línea 820: Línea 872:
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| B | $00 | B' | $A0 |+| B | $00 | B' | $a0 |
 | C | $00 | C' | $55 | | C | $00 | C' | $55 |
 | D | $00 | D' | $01 | | D | $00 | D' | $01 |
-| E | $00 | E' | $FF |+| E | $00 | E' | $ff |
 | H | $00 | H' | $00 | | H | $00 | H' | $00 |
 | L | $00 | L' | $31 | | L | $00 | L' | $31 |
Línea 829: Línea 881:
 Si realizamos de nuevo ''EXX'', volveremos a dejar los valores de los registros en sus "posiciones" originales. ''EXX'' (mnemónico ensamblador derivado de EXchange), simplemente intercambia los valores entre ambos bancos. Si realizamos de nuevo ''EXX'', volveremos a dejar los valores de los registros en sus "posiciones" originales. ''EXX'' (mnemónico ensamblador derivado de EXchange), simplemente intercambia los valores entre ambos bancos.
  
-Aparte de la instrucción ''EXX'', disponemos de una instrucción ''EX AFAF<nowiki>'</nowiki>'' , que, como el lector imagina, intercambia los valores de los registros AF y AF'. Así, pasaríamos de:+Aparte de la instrucción ''EXX'', disponemos de una instrucción ''ex afaf<nowiki>'</nowiki>'' , que, como el lector imagina, intercambia los valores de los registros AF y AF'. Así, pasaríamos de:
  
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| A | 01h | A' | 00h +| A | $01 | A' | $00 
-| F | 10h | F' | 00h |+| F | $10 | F' | $00 |
  
 a: a:
Línea 840: Línea 892:
 |< 60% >| |< 60% >|
 ^ Registro ^ Valor ^ Registro ^ Valor ^ ^ Registro ^ Valor ^ Registro ^ Valor ^
-| A | 00h | A' | 01h +| A | $00 | A' | $01 
-| F | 00h | F' | 10h |+| F | $00 | F' | $10 |
  
-Realizando de nuevo un ''EX AFAF<nowiki>'</nowiki>'' volveríamos a los valores originales en ambos registros.+Realizando de nuevo un ''ex afaf<nowiki>'</nowiki>'' volveríamos a los valores originales en ambos registros.
  
 De esta forma podemos disponer de un set de registros extra Acumulador/Flags con los que trabajar. Por ejemplo, supongamos que programamos una porción de código donde queremos hacer una serie de cálculos entre registros y después dejar el resultado en una posición de memoria, pero no queremos perder los valores actuales de los registros (ni tampoco hacer uso de la pila, que veremos en su momento). En ese caso, podemos hacer: De esta forma podemos disponer de un set de registros extra Acumulador/Flags con los que trabajar. Por ejemplo, supongamos que programamos una porción de código donde queremos hacer una serie de cálculos entre registros y después dejar el resultado en una posición de memoria, pero no queremos perder los valores actuales de los registros (ni tampoco hacer uso de la pila, que veremos en su momento). En ese caso, podemos hacer:
Línea 853: Línea 905:
  
     ; Cambiamos de banco de registros:     ; Cambiamos de banco de registros:
-    EXX +    exx 
-    EX AFAF                      ; Intercambiamos AF con AF'+    ex afaf                      ; Intercambiamos AF con AF'
  
     ; Hacemos nuestras operaciones     ; Hacemos nuestras operaciones
-    LD A, ($1234) +    ld a, ($1234) 
-    LD BA +    ld ba 
-    LD A, ($1235) +    ld a, ($1235) 
-    INC A +    inc a 
-    ADD AB+    add ab
     ; (...etc...)     ; (...etc...)
     ; (...aquí más operaciones...)     ; (...aquí más operaciones...)
  
     ; Grabamos el resultado en memoria     ; Grabamos el resultado en memoria
-    LD ($1236), A+    ld ($1236), a
  
-    ; Recuperamos los valores de los registros +    ; Recuperamos los registros: 
-    EX AFAF                      ; Intercambiamos AF con AF' +    ex afaf                      ; Intercambiamos AF con AF' 
-    EXX+    exx
  
     ; Volvemos al lugar de llamada de la rutina     ; Volvemos al lugar de llamada de la rutina
-    RET+    ret
 </code> </code>
  
Línea 879: Línea 931:
  
 <code z80> <code z80>
-    EXX+    exx
  
     ; ... Realizamos una serie de operaciones complejas ...     ; ... Realizamos una serie de operaciones complejas ...
  
     ; Guardamos en la pila el valor de HL     ; Guardamos en la pila el valor de HL
-    PUSH HL+    push hl
  
     ; Recuperamos el juego de registros original     ; Recuperamos el juego de registros original
-    EXX+    exx
  
     ; Obtenemos de la pila el valor calculado     ; Obtenemos de la pila el valor calculado
-    POP HL +    pop hl 
-    RET+    ret
 </code> </code>
  
Línea 898: Línea 950:
 |< 50% >| |< 50% >|
 ^ Instrucción ^ Resultado ^ ^ Instrucción ^ Resultado ^
-EX DEHL | Intercambiar los valores de DE y HL. |  +ex dehl | Intercambiar los valores de DE y HL. |  
-EX (SP), HL | Intercambiar el valor de HL con el valor de 16 bits\\ de la posición de memoria apuntada por el registro SP\\ (por ejemplo, para intercambiar el valor de HL con el\\ del último registro que hayamos introducido en la pila). | +ex (sp), hl | Intercambiar el valor de HL con el valor de 16 bits\\ de la posición de memoria apuntada por el registro SP\\ (por ejemplo, para intercambiar el valor de HL con el\\ del último registro que hayamos introducido en la pila). | 
-EX (SP), IX | Igual que el anterior, pero con IX. | +ex (sp), ix | Igual que el anterior, pero con IX. | 
-EX (SP), IY | Igual que el anterior, pero con IY. |+ex (sp), iy | Igual que el anterior, pero con IY. |
  
 La primera de estas instrucciones nos resultará muy útil en nuestros programas en ensamblador, ya que nos permite intercambiar los valores de los registros DE y HL. Las 3 instrucciones restantes permiten intercambiar el valor apuntado por SP (en memoria) por el valor de los registros HL, IX o IY. La primera de estas instrucciones nos resultará muy útil en nuestros programas en ensamblador, ya que nos permite intercambiar los valores de los registros DE y HL. Las 3 instrucciones restantes permiten intercambiar el valor apuntado por SP (en memoria) por el valor de los registros HL, IX o IY.
  
-Como ya hemos comentado cuando hablamos del carácter Low-Endian de nuestra CPU, al escribir en memoria (también en la pila) primero se escribe el Byte Bajo y luego el Byte Alto. Posteriormente lo leeremos de la misma forma, de tal modo que si los bytes apuntados en la pila (en memoria) son "$FF $00", al hacer el EX (SP), HL, el registro HL valdrá "$00FF".+Como ya hemos comentado cuando hablamos del carácter Low-Endian de nuestra CPU, al escribir en memoria (también en la pila) primero se escribe el Byte Bajo y luego el Byte Alto. Posteriormente lo leeremos de la misma forma, de tal modo que si los bytes apuntados en la pila (en memoria) son **$ff $00**, al hacer el ''ex (sp), hl'', el registro HL valdrá **$00ff**.
  
 Nótese que aprovechando la pila (como veremos en su momento) también podemos intercambiar los valores de los registros mediante: Nótese que aprovechando la pila (como veremos en su momento) también podemos intercambiar los valores de los registros mediante:
  
 <code z80> <code z80>
-PUSH BC +push bc 
-PUSH DE +push de 
-POP BC +pop bc 
-POP DE+pop de
 </code> </code>
  
Línea 919: Línea 971:
  
 <code z80> <code z80>
-PUSH HL +push hl 
-LD LC +ld lc 
-LD HB +ld hb 
-POP BC+pop bc
 </code> </code>
  
Línea 932: Línea 984:
 Como hemos visto, con los Shadow Registers tenemos un set de registros adicional donde hacer cálculos, algo que parece en principio maravilloso si necesitamos más registros para realizar operaciones y no queremos acceder ni a memoria ni a la pila para almacenar datos o valores intermedios. Como hemos visto, con los Shadow Registers tenemos un set de registros adicional donde hacer cálculos, algo que parece en principio maravilloso si necesitamos más registros para realizar operaciones y no queremos acceder ni a memoria ni a la pila para almacenar datos o valores intermedios.
  
-Existen algunas restricciones para el uso de los Shadow Registers (pero que como veremos, no nos afectan en el Spectrum): Si la ROM de nuestro sistema, en su rutina de gestión de interrupciones (ISR) utiliza los Shadow Registers, necesitaremos deshabilitar las interrupciones con **DI** (Disable Interrupts) antes de usar "EXX" y volver a habilitarlas con **EI** (Enable Interrupts") cuando hayamos finalizado de trabajar con ellos. Afortunadamente, en el ZX Spectrum, la ISR del modo estándar de interrupciones (IM1, el modo en que funciona el Spectrum desde que lo arrancamos) no utiliza los Shadow Registers.+Existen algunas restricciones para el uso de los Shadow Registers (pero que como veremos, no nos afectan en el Spectrum): Si la ROM de nuestro sistema, en su rutina de gestión de interrupciones (ISR) utiliza los Shadow Registers, necesitaremos deshabilitar las interrupciones con **di** (Disable Interrupts) antes de usar "exx" y volver a habilitarlas con **ei** (Enable Interrupts") cuando hayamos finalizado de trabajar con ellos. Afortunadamente, en el ZX Spectrum, la ISR del modo estándar de interrupciones (im1, el modo en que funciona el Spectrum desde que lo arrancamos) no utiliza los Shadow Registers.
  
 Pero los Shadow Registers tienen una desventaja muy grande y que ya hemos visto, y es que no podemos utilizarlos directamente (no existen instrucciones para operar con ellos) y tampoco podemos usarlos a la vez que los registros normales. Pero los Shadow Registers tienen una desventaja muy grande y que ya hemos visto, y es que no podemos utilizarlos directamente (no existen instrucciones para operar con ellos) y tampoco podemos usarlos a la vez que los registros normales.
Línea 938: Línea 990:
 Lo único que podemos hacer es intercambiar los registros actuales con los alternativos y viceversa, con lo cual nuestra posibilidad de operar entre los registros normales y los Shadow es muy limitada. Sí, tenemos un set de registros extra para hacer operaciones, pero no podemos pasar directamente los operandos que tenemos actualmente en BC, DE o HL al otro set para hacer dichas operaciones. Lo único que podemos hacer es intercambiar los registros actuales con los alternativos y viceversa, con lo cual nuestra posibilidad de operar entre los registros normales y los Shadow es muy limitada. Sí, tenemos un set de registros extra para hacer operaciones, pero no podemos pasar directamente los operandos que tenemos actualmente en BC, DE o HL al otro set para hacer dichas operaciones.
  
-Debemos almacenar estos valores en la pila, la memoria, o el registro AF (si ejecutamos ''EXX'' pero no ''EX AFAF<nowiki>'</nowiki>'') para que después del intercambio podamos hacer operaciones con estos valores en los nuevos registros. Y para devolvernos el valor resultado de la operación a los registros convencionales tendremos el mismo problema.+Debemos almacenar estos valores en la pila, la memoria, o el registro AF (si ejecutamos ''EXX'' pero no ''ex afaf<nowiki>'</nowiki>'') para que después del intercambio podamos hacer operaciones con estos valores en los nuevos registros. Y para devolvernos el valor resultado de la operación a los registros convencionales tendremos el mismo problema.
  
 Es decir, que usar los Shadow Registers implica utilizar la memoria o la pila, de modo que si ya tenemos que hacer esto... ¿por qué no usar la memoria o la pila directamente para salvaguardar datos de nuestros registros normales cuando lo necesitemos? Es decir, que usar los Shadow Registers implica utilizar la memoria o la pila, de modo que si ya tenemos que hacer esto... ¿por qué no usar la memoria o la pila directamente para salvaguardar datos de nuestros registros normales cuando lo necesitemos?
Línea 944: Línea 996:
 Salvo excepciones, lo habitual en lugar de utilizar los Shadow Registers como registros extra es utilizar los registros estándar salvaguardando sus valores cuando lo necesitemos en la pila, en la memoria, o (esto es un truco algo más avanzado) en el propio código que se va a ejecutar después. Salvo excepciones, lo habitual en lugar de utilizar los Shadow Registers como registros extra es utilizar los registros estándar salvaguardando sus valores cuando lo necesitemos en la pila, en la memoria, o (esto es un truco algo más avanzado) en el propio código que se va a ejecutar después.
  
-Veamos los 3 ejemplos (sin usar EXX):+Veamos los 3 ejemplos (sin usar exx):
  
 **Opción 1.- Usar la pila:** **Opción 1.- Usar la pila:**
  
 <code z80> <code z80>
-    LD B, 8 +    ld b, 8 
-    PUSH BC              ; Necesitamos salvaguardar BC +    push bc              ; Necesitamos salvaguardar BC 
-    LD B, 9              ; (porque vamos a usarlo para algo)+    ld b, 9              ; (porque vamos a usarlo para algo) 
     ... hacer algo con BC ...     ... hacer algo con BC ...
-    POP BC               ; recuperar el valor de BC+     
 +    pop bc               ; recuperar el valor de BC
 </code> </code>
  
Línea 959: Línea 1013:
  
 <code z80> <code z80>
-    LD B, 8 +    ld b, 8 
-    LD (1000), BC        ; Necesitamos salvaguardar BC +    ld (1000), bc        ; Necesitamos salvaguardar BC 
-    LD B,9               ; (porque vamos a usarlo para algo)+    ld b,9               ; (porque vamos a usarlo para algo) 
 +    
     .....     .....
-    LD (1002), BC        ; guardamos el resultado +     
-    LD BC, (1000)        ; restauramos el valor de BC+    ld (1002), bc        ; guardamos el resultado 
 +    ld bc, (1000)        ; restauramos el valor de BC
     ....     ....
 </code> </code>
Línea 973: Línea 1029:
  
 <code z80> <code z80>
-    LD (save_bc+1), BC    ; Escribimos BC en la parte NN NN del +    ld (save_bc+1), bc    ; Escribimos BC en la parte NN NN del 
-                          ; opcode "LD BC, NN NN" en memoria+                          ; opcode "ld bc, NN NN" en memoria
  
     ... hacer cosas con BC, perdiendo su valor ...     ... hacer cosas con BC, perdiendo su valor ...
  
 save_bc: save_bc:
-    LD BC, $0000          ; En el LD anterior cambiamos $000+    ld bc, $0000          ; En el LD anterior cambiamos $000
                           ; por el valor de BC, así que cuando                           ; por el valor de BC, así que cuando
-                          ; el z80 llegue aquí no es ya LD BC, 0 +                          ; el z80 llegue aquí no es ya ld bc, 0 
-                          ; sino LD BC, valor_que_tenia_BC+                          ; sino ld bc, valor_que_tenia_BC
                           ; así que recuperaremos BC aquí.                           ; así que recuperaremos BC aquí.
 </code> </code>
  
-El ejemplo anterior es muy interesante. Cuando hacemos el ''LD (save_bc+1), BC'', estamos sobreescribiendo nuestro propio programa. Concretamente, lo que hacemos es CAMBIAR el opcode que estaba ensamblado (''LD BC, $0000'', que en memoria sería "**$01 $00 $00**") por **$01 XX XX**.+El ejemplo anterior es muy interesante. Cuando hacemos el ''ld (save_bc+1), bc'', estamos sobreescribiendo nuestro propio programa. Concretamente, lo que hacemos es CAMBIAR el opcode que estaba ensamblado (''ld bc, $0000'', que en memoria sería "**$01 $00 $00**") por **$01 XX XX**.
  
-En este caso ''save_bc'' apuntaría al $01, y con ''save_bc+1'' lo que hacemos es escribir después del opcode de ''LD BC,'', en la parte del opcode que contiene el valor a cargar en el registro. Cuando se ejecuta el ''LD (save_bc+1), BC'', estamos escribiendo el valor de BC en ese momento encima de "XX XX" de forma que cuando la ejecución del programa continúe y lleguemos a ese punto, el comando ''LD BC, XX'' se ejecutará y recuperará en BC el valor que tenía BC cuando se almacenó en esa posición de memoria.+En este caso ''save_bc'' apuntaría al $01, y con ''save_bc+1'' lo que hacemos es escribir después del opcode de ''ld bc,'', en la parte del opcode que contiene el valor a cargar en el registro. Cuando se ejecuta el ''ld (save_bc+1), bc'', estamos escribiendo el valor de BC en ese momento encima de "XX XX" de forma que cuando la ejecución del programa continúe y lleguemos a ese punto, el comando ''ld bc, XX'' se ejecutará y recuperará en BC el valor que tenía BC cuando se almacenó en esa posición de memoria.
  
-Con esto, estamos preservando el valor del registro a cambio de una escritura en memoria (''LD (nn), BC'' = 20 ciclos de reloj) y de su posterior asignación (10 ciclos), un total de 30 ciclos de reloj. La alternativa de PUSH POP utilizaría 21 ciclos en total (11 el ''PUSH'' y 10 el ''POP'').+Con esto, estamos preservando el valor del registro a cambio de una escritura en memoria (''ld (nn), bc'' = 20 ciclos de reloj) y de su posterior asignación (10 ciclos), un total de 30 ciclos de reloj. La alternativa con PUSH/POP utilizaría 21 ciclos en total (11 el ''PUSH'' y 10 el ''POP'').
  
 \\  \\ 
Línea 1004: Línea 1060:
 ===== Ficheros ===== ===== Ficheros =====
  
-   * {{cursos:ensamblador:03_ejemplo.asm|Ejemplo de programa en ASM}} 
-   * {{cursos:ensamblador:03_ejemplo.tap|Fichero tap del ejemplo ejemplo.asm}} 
    * {{cursos:ensamblador:03_cambio.asm|Programa en ASM que muestra el uso de la pila para el intercambio de registros}}    * {{cursos:ensamblador:03_cambio.asm|Programa en ASM que muestra el uso de la pila para el intercambio de registros}}
    * {{cursos:ensamblador:03_cambio.tap|Fichero tap del ejemplo cambio.asm}}    * {{cursos:ensamblador:03_cambio.tap|Fichero tap del ejemplo cambio.asm}}
  • cursos/ensamblador/lenguaje_1.1705307089.txt.gz
  • Última modificación: 15-01-2024 08:24
  • por sromero