cursos:ensamblador:lenguaje_3

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_3 [14-01-2024 16:18] sromerocursos:ensamblador:lenguaje_3 [19-01-2024 07:14] (actual) sromero
Línea 19: Línea 19:
     ORG 50000     ORG 50000
  
-    NOP +    nop 
-    LD B, 10+    ld b, 10
  
 bucle: bucle:
-    LD A, 20 +    ld a, 20 
-    NOP+    nop
     (...)     (...)
-    JP bucle +    jp bucle 
-    RET+    ret
 </code> </code>
  
Línea 40: Línea 40:
 |< 50% 33% 33% 33% >| |< 50% 33% 33% 33% >|
 ^ DIRECCION ^ OPCODE ^ INSTRUCCION ^ ^ DIRECCION ^ OPCODE ^ INSTRUCCION ^
-| 50000 | 00 | NOP +| 50000 | 00 | nop 
-| 50001 | 06 0a | LD B, 10 | +| 50001 | 06 0a | ld b, 10 | 
-| 50003 | 3e 14 | LD A, 20 | +| 50003 | 3e 14 | ld a, 20 | 
-| 50004 | 00 | NOP |+| 50004 | 00 | nop |
 | ... | ... | ... | | ... | ... | ... |
-| ... | c3 53 c3 | JP $C353   (53000) | +| ... | c3 53 c3 | jp $c353   (53000) | 
-| ...+1 | c9 | RET |+| ...+1 | c9 | ret |
  
 Si mostramos las direcciones de memoria en que se ensambla cada instrucción, Si mostramos las direcciones de memoria en que se ensambla cada instrucción,
Línea 52: Línea 52:
  
 <code> <code>
-50000   NOP              ; (opcode = 1 byte) +50000   nop              ; (opcode = 1 byte) 
-50001   LD B, 10         ; (opcode = 2 bytes) +50001   ld b, 10         ; (opcode = 2 bytes) 
-50003   LD A, 20         ; (opcode = 2 bytes) +50003   ld a, 20         ; (opcode = 2 bytes) 
-50005   NOP              ; (opcode = 1 byte)+50005   nop              ; (opcode = 1 byte)
 50005   (más código) 50005   (más código)
 50006   (más código) 50006   (más código)
 ..... .....
-50020   JP bucle +50020   jp bucle 
-50023   RET+50023   ret
 </code> </code>
  
Línea 67: Línea 67:
  
  En nuestro ejemplo anterior, le decimos al programa ensamblador mediante ''ORG 50000'' que nuestro código, una vez ensamblado, debe quedar  En nuestro ejemplo anterior, le decimos al programa ensamblador mediante ''ORG 50000'' que nuestro código, una vez ensamblado, debe quedar
-situado a partir de la dirección 50000, con lo cual cuando calcule las direcciones de las etiquetas deberá hacerlo en relación a esta dirección de origen. Así, en nuestro ejemplo anterior la instrucción ''NOP'', que se ensambla con el opcode $00, será "pokeada" (por nuestro cargador BASIC) en la dirección 50000. La instrucción LD B, 10, cuyo opcode tiene 2 bytes,  será "pokeada" en 50001 y 50002, y así con todas las instrucciones del programa.+situado a partir de la dirección 50000, con lo cual cuando calcule las direcciones de las etiquetas deberá hacerlo en relación a esta dirección de origen. Así, en nuestro ejemplo anterior la instrucción ''NOP'', que se ensambla con el opcode $00, será "pokeada" (por nuestro cargador BASIC) en la dirección 50000. La instrucción ld b, 10, cuyo opcode tiene 2 bytes,  será "pokeada" en 50001 y 50002, y así con todas las instrucciones del programa.
  
- Cuando el ensamblador se encuentra la etiqueta ''bucle'' después del ''LD B, 10'', ¿cómo la ensambla? Supuestamente le corresponde la+ Cuando el ensamblador se encuentra la etiqueta ''bucle'' después del ''ld b, 10'', ¿cómo la ensambla? Supuestamente le corresponde la
 posición 50003, pero recordemos que esto no es una instrucción, sino una etiqueta: no tiene ningún significado para el microprocesador, posición 50003, pero recordemos que esto no es una instrucción, sino una etiqueta: no tiene ningún significado para el microprocesador,
 sólo para el programa ensamblador. Por eso, cuando el ensamblador encuentra la etiqueta ''bucle'', asocia internamente esta etiqueta (el sólo para el programa ensamblador. Por eso, cuando el ensamblador encuentra la etiqueta ''bucle'', asocia internamente esta etiqueta (el
Línea 81: Línea 81:
 \\  \\ 
  
- Lo que realmente ensamblará en la dirección 50003 (y en la 50004) es la instrucción siguiente: ''LD A, 20''.+ Lo que realmente ensamblará en la dirección 50003 (y en la 50004) es la instrucción siguiente: ''ld a, 20''.
  
  Pero, entonces, ¿para qué nos //sirve// la etiqueta? Sencillo: //para poder hacer referencia en cualquier momento a esa posición de memoria// (del programa, en este caso), mediante una cadena fácil de recordar en lugar de mediante un número. Es más sencillo recordar "bucle" que recordar "50003", y si nuestro programa es largo y tenemos muchos saltos, funciones o variables, acabaremos utilizando decenas y centenares de números para saltos, con lo que el programa sería inmanejable.  Pero, entonces, ¿para qué nos //sirve// la etiqueta? Sencillo: //para poder hacer referencia en cualquier momento a esa posición de memoria// (del programa, en este caso), mediante una cadena fácil de recordar en lugar de mediante un número. Es más sencillo recordar "bucle" que recordar "50003", y si nuestro programa es largo y tenemos muchos saltos, funciones o variables, acabaremos utilizando decenas y centenares de números para saltos, con lo que el programa sería inmanejable.
Línea 90: Línea 90:
     ORG 50000     ORG 50000
  
-    NOP +    nop 
-    LD B, 10 +    ld b, 10 
-    LD A, 20 +    ld a, 20 
-    NOP+    nop
     (...)     (...)
-    JP 50003 +    jp 50003 
-    RET+    ret
  
     END     END
 </code> </code>
  
- En este caso, ''JP 50003'' no permite distinguir rápidamente a qué instrucción vamos a saltar, mientras que la etiqueta "bucle" que utilizamos en el anterior ejemplo marcaba de forma indiscutible el destino del salto.+ En este caso, ''jp 50003'' no permite distinguir rápidamente a qué instrucción vamos a saltar, mientras que la etiqueta "bucle" que utilizamos en el anterior ejemplo marcaba de forma indiscutible el destino del salto.
  
  Las etiquetas son muy útiles no sólo por motivos de legibilidad del código.  Las etiquetas son muy útiles no sólo por motivos de legibilidad del código.
  
-Imaginemos que una vez acabado nuestro programa sin etiquetas (utilizando sólo direcciones numéricas), con muchos saltos (''JP'', ''CALL'', ''JR'', ''DJNZ''...) a diferentes partes del mismo, tenemos que modificarlo para corregir alguna parte del mismo. Al añadir o quitar instrucciones del programa, estamos variando las posiciones donde se ensambla todo el programa. Si por ejemplo,+Imaginemos que una vez acabado nuestro programa sin etiquetas (utilizando sólo direcciones numéricas), con muchos saltos (''jp'', ''call'', ''jr'', ''DJNZ''...) a diferentes partes del mismo, tenemos que modificarlo para corregir alguna parte del mismo. Al añadir o quitar instrucciones del programa, estamos variando las posiciones donde se ensambla todo el programa. Si por ejemplo,
 añadiéramos un ''NOP'' extra al principio del mismo, ya no habría que saltar a 50003 sino a 50004: añadiéramos un ''NOP'' extra al principio del mismo, ya no habría que saltar a 50003 sino a 50004:
  
Línea 111: Línea 111:
     ORG 50000     ORG 50000
  
-    NOP +    nop 
-    NOP        ; Un NOP extra +    nop        ; Un nop extra 
-    LD B, 10 +    ld b, 10 
-    LD A, 20 +    ld a, 20 
-    NOP+    nop
     (...)     (...)
-    JP 50004   ; La dirección de salto cambia +    jp 50004   ; La dirección de salto cambia 
-    RET+    ret
  
     END     END
 </code> </code>
  
- Para que nuestro programa funcione, tendríamos que cambiar TODAS las direcciones numéricas de salto del programa, a mano (recalculandolas todas). Las etiquetas evitan esto, ya que es el programa ensamblador quien, en tiempo de ensamblado, cuando está convirtiendo el programa a código objeto, cambia todas las referencias a la etiqueta por el valor numérico correcto (por la posición donde aparece la etiqueta). Un ''JP bucle'' siempre saltaría a la dirección correcta (la de la posición de la etiqueta) aunque cambiemos la cantidad de instrucciones del programa.+ Para que nuestro programa funcione, tendríamos que cambiar TODAS las direcciones numéricas de salto del programa, a mano (recalculandolas todas). Las etiquetas evitan esto, ya que es el programa ensamblador quien, en tiempo de ensamblado, cuando está convirtiendo el programa a código objeto, cambia todas las referencias a la etiqueta por el valor numérico correcto (por la posición donde aparece la etiqueta). Un ''jp bucle'' siempre saltaría a la dirección correcta (la de la posición de la etiqueta) aunque cambiemos la cantidad de instrucciones del programa.
  
- Como veremos posteriormente, la instrucción JP realiza un salto de ejecución de código a una posición de memoria dada. Literalmente, un + Como veremos posteriormente, la instrucción jp realiza un salto de ejecución de código a una posición de memoria dada. Literalmente, un 
-''JP NNNN'' hace el registro ''PC = NNNN'', de forma que alteramos el orden de ejecución del programa. Las etiquetas nos permiten establecer posiciones donde saltar en nuestro programa para utilizarlas luego fácilmente:+''jp NNNN'' hace el registro ''PC = NNNN'', de forma que alteramos el orden de ejecución del programa. Las etiquetas nos permiten establecer posiciones donde saltar en nuestro programa para utilizarlas luego fácilmente:
  
 <code z80> <code z80>
Línea 134: Línea 134:
 RutinaLeerTeclado: RutinaLeerTeclado:
    (instrucciones)    ; Aquí código    (instrucciones)    ; Aquí código
-   RET+   ret
  
-; Saltar (JP) a esta rutina con:+; Saltar (jp) a esta rutina con:
 ;  HL = Sprite a dibujar ;  HL = Sprite a dibujar
 ;  DE = Direccion en pantalla donde dibujar ;  DE = Direccion en pantalla donde dibujar
Línea 147: Línea 147:
 pintar: pintar:
     (instrucciones)     (instrucciones)
-    JP bucle1+    jp bucle1
     (...)     (...)
  salir:  salir:
-    RET+    ret
  
 (etc...) (etc...)
Línea 165: Línea 165:
 etiqueta: etiqueta:
     ;;; (más código)     ;;; (más código)
-    JP etiqueta+    jp etiqueta
 </code> </code>
  
Línea 171: Línea 171:
  
 <code z80> <code z80>
-    JP etiqueta+    jp etiqueta
     ;;; (más código)     ;;; (más código)
 etiqueta: etiqueta:
Línea 187: Línea 187:
     DB 0     DB 0
     DB "Esto es una cadena"     DB "Esto es una cadena"
-    DB "Cadena con fin", $FF+    DB "Cadena con fin", $ff
     DW 12345     DW 12345
 </code> </code>
Línea 204: Línea 204:
 bucle: bucle:
     ...     ...
-    RET+    ret
  
  
Línea 225: Línea 225:
  
     ; Primero vamos a copiar unos datos a la videomemoria.     ; Primero vamos a copiar unos datos a la videomemoria.
-    LD HL, datos                ; origen +    ld hl, datos                ; origen 
-    LD DE, 16384                ; destino +    ld de, 16384                ; destino 
-    LD BC, (longitud_datos)     ; longitud (BC son 2 bytes, var es DW) +    ld bc, (longitud_datos)     ; longitud (BC son 2 bytes, var es DW) 
-    LDIR+    ldir
  
-    LD DE, 10                   ; D = 0, E = 10 +    ld de, 10                   ; D = 0, E = 10 
-    CALL CursorAt+    call CursorAt
  
-    LD DE, texto +    ld de, texto 
-    CALL PrintString            ; Imprimimos cadena+    call PrintString            ; Imprimimos cadena
  
     ; Ahora vamos a sumar 1 a cada caracter de la cadena,     ; Ahora vamos a sumar 1 a cada caracter de la cadena,
     ; un total de "repeticiones" veces:     ; un total de "repeticiones" veces:
-    LD HL, texto                ; Origen +    ld hl, texto                ; Origen 
-    LD A, (repeticiones)        ; A +    ld a, (repeticiones)        ; A 
-    LD B                    ; B = A (para DJNZ)+    ld b                    ; B = A (para djnz)
  
 bucle: bucle:
-    INC (HL)                    ; Incrementamos el valor apuntado por HL +    inc (hl)                    ; Incrementamos el valor apuntado por HL 
-    INC HL                      ; Siguiente posicion de la cadena+    inc hl                      ; Siguiente posicion de la cadena
  
-    DJNZ bucle                  ; Repetir "variable (27)" veces+    djnz bucle                  ; Repetir "variable (27)" veces
  
-    LD DE, texto +    ld de, texto 
-    CALL PrintString            ; Imprimimos cadena transformada+    call PrintString            ; Imprimimos cadena transformada
  
-    LD A, 1 +    ld a, 1 
-    LD (fin),                 ; Cambiamos valor de variable+    ld (fin),                 ; Cambiamos valor de variable
  
-    LD BC, (variable_16bit)     ; Ahora BC vale 12345 +    ld bc, (variable_16bit)     ; Ahora BC vale 12345 
-    CALL PrintNum               ; imprimir su valor+    call PrintNum               ; imprimir su valor
  
-    CALL PrintSpace +    call PrintSpace 
-    LD A, (fin)                 ; Ahora BC debería valer 1 +    ld a, (fin)                 ; Ahora BC debería valer 1 
-    LD B, 0 +    ld b, 0 
-    LD C                    ; BC = A (B=0 y C=A) +    ld c                    ; BC = A (B=0 y C=A) 
-    CALL PrintNum+    call PrintNum
  
-    RET+    ret
  
-datos           DB 0, $FF, $FF, 0, $FF, 12, 0, 0, 0, 10, 255+datos           DB 0, $ff, $ff, 0, $ff, 12, 0, 0, 0, 10, 255
 longitud_datos  DW 10 longitud_datos  DW 10
  
Línea 290: Línea 290:
 El programa ensamblador sabe en qué posición de la memoria estarán todas las etiquetas (variables, nombres de funciones, etiquetas para saltos) cuando lo ejecutemos, porque le hemos definido una dirección ''ORG'' al principio. A partir de ella, en el proceso de ensamblado, se van convirtiendo instrucciones en opcodes, e insertando datos, y el ensamblador puede ser en qué dirección exacta acabará en memoria cada instrucción, variable, etiqueta o rutina. El programa ensamblador sabe en qué posición de la memoria estarán todas las etiquetas (variables, nombres de funciones, etiquetas para saltos) cuando lo ejecutemos, porque le hemos definido una dirección ''ORG'' al principio. A partir de ella, en el proceso de ensamblado, se van convirtiendo instrucciones en opcodes, e insertando datos, y el ensamblador puede ser en qué dirección exacta acabará en memoria cada instrucción, variable, etiqueta o rutina.
  
-Por eso, si por ejemplo utilizamos una dirección ORG 50000, pero luego cargamos el programa en 40000 y saltamos a 40000, las referencias que hay en el código apuntarán a direcciones de memoria incorrectas (por ejemplo direcciones en el rango 50XXX). El código comenzará a ejecutarse bien empezando en 40000, se irá ejecutando nuestro programa opcode a opcode, pero cuando la ejecución del programa llegue a cualquier salto a rutina (''CALL'') o etiqueta (''JP'', ''JR'', ''DJNZ'', etc), la dirección destino del salto que habrá en nuestro programa será la que introdujo el programa ensamblador con ORG 40000, es decir, serán direcciones 40xxx, y el salto se realizará a una zona de memoria donde no está nuestro código y el programa se colgará. De la misma forma, referencias a "variables" definidas con DB/DW apuntarán a zonas de la memoria donde no está realmente nuestra variable.+Por eso, si por ejemplo utilizamos una dirección ORG 50000, pero luego cargamos el programa en 40000 y saltamos a 40000, las referencias que hay en el código apuntarán a direcciones de memoria incorrectas (por ejemplo direcciones en el rango 50XXX). El código comenzará a ejecutarse bien empezando en 40000, se irá ejecutando nuestro programa opcode a opcode, pero cuando la ejecución del programa llegue a cualquier salto a rutina (''call'') o etiqueta (''jp'', ''jr'', ''DJNZ'', etc), la dirección destino del salto que habrá en nuestro programa será la que introdujo el programa ensamblador con ORG 40000, es decir, serán direcciones 40xxx, y el salto se realizará a una zona de memoria donde no está nuestro código y el programa se colgará. De la misma forma, referencias a "variables" definidas con DB/DW apuntarán a zonas de la memoria donde no está realmente nuestra variable.
  
-Los datos, en nuestro programa de ejemplo, están situados en la memoria, justo después de las instrucciones ensambladas (tras el RET posterior al PrintNum final). Podemos verlo si ensamblamos el programa y examinamos el resultado del ensamblado:+Los datos, en nuestro programa de ejemplo, están situados en la memoria, justo después de las instrucciones ensambladas (tras el ret posterior al PrintNum final). Podemos verlo si ensamblamos el programa y examinamos el resultado del ensamblado:
  
 <code> <code>
Línea 322: Línea 322:
  El contenido del binario que estamos viendo arriba es lo que el cargador BASIC "pokearía" en memoria (o cargaría con ''LOAD "" CODE'').  El contenido del binario que estamos viendo arriba es lo que el cargador BASIC "pokearía" en memoria (o cargaría con ''LOAD "" CODE'').
  
- El primer opcode es "**$21**", que es el opcode correspondiente a ''LD HL, NNNN'' (nuestro programa empieza por ''LD HL, datos'').+ El primer opcode es "**$21**", que es el opcode correspondiente a ''ld hl, NNNN'' (nuestro programa empieza por ''ld hl, datos'').
  
- Los 2 siguientes bytes son pues la dirección de la etiqueta "datos". En este caso, estos 2 bytes son **$92 $C3**, siendo el primer número el byte bajo y el segundo el byte alto del valor NNNN, es decir, **$C392** o **50066**. Esa dirección de memoria, 50666, es donde están alojados los bytes **0, $FF, $FF, 0, $FF, 12, 0, 0, 0, 10, 255** que hay tras la etiqueta ''datos''.+ Los 2 siguientes bytes son pues la dirección de la etiqueta "datos". En este caso, estos 2 bytes son **$92 $c3**, siendo el primer número el byte bajo y el segundo el byte alto del valor NNNN, es decir, **$c392** o **50066**. Esa dirección de memoria, 50666, es donde están alojados los bytes **0, $ff, $ff, 0, $ff, 12, 0, 0, 0, 10, 255** que hay tras la etiqueta ''datos''.
  
- A continuación podemos ver el opcode **$11 $00 $40** que se corresponde con ''LD DE, 16384'' (16384 es $4000).+ A continuación podemos ver el opcode **$11 $00 $40** que se corresponde con ''ld de, 16384'' (16384 es $4000).
  
 Así, el ensamblador va avanzando en el ensamblado del programa e insertando los opcodes y sus operandos en el binario resultante. Así, el ensamblador va avanzando en el ensamblado del programa e insertando los opcodes y sus operandos en el binario resultante.
Línea 332: Línea 332:
 Cuando durante el proceso de ensamblado, el ensamblador se encuentra una etiqueta, la reemplaza en el binario resultante por la dirección de memoria donde está dicha etiqueta. Si la etiqueta ya se definió anteriormente, el ensamblador sabe en qué dirección de memoria estaba, y si es una etiqueta que aparece más adelante en el código, deja 2 bytes (por ejemplo, dos ceros) y al acabar el ensamblado (cuando la etiqueta ya ha aparecido) los reemplaza por la dirección de la misma. Cuando durante el proceso de ensamblado, el ensamblador se encuentra una etiqueta, la reemplaza en el binario resultante por la dirección de memoria donde está dicha etiqueta. Si la etiqueta ya se definió anteriormente, el ensamblador sabe en qué dirección de memoria estaba, y si es una etiqueta que aparece más adelante en el código, deja 2 bytes (por ejemplo, dos ceros) y al acabar el ensamblado (cuando la etiqueta ya ha aparecido) los reemplaza por la dirección de la misma.
  
- Pero sigamos viendo el resultado del ensamblado: podemos ver en el "binario" los datos de nuestro programa mezclados con el código del mismo, hasta el opcode de ''RET'' (201, o **$C9**) justo antes del bloque de datos en la línea "00000040" del listado de arriba ("00000040  c3 **c9** 00 ff ff 00 ff 0c").+ Pero sigamos viendo el resultado del ensamblado: podemos ver en el "binario" los datos de nuestro programa mezclados con el código del mismo, hasta el opcode de ''RET'' (201, o **$c9**) justo antes del bloque de datos en la línea "00000040" del listado de arriba ("00000040  c3 **c9** 00 ff ff 00 ff 0c").
  
-Después de ese ''RET'' vienen los datos (podemos ver la cadena de texto) hasta el final de la línea 00000060 y principio de 00000070, donde vemos **$0D $0D $FF**. Estos son los ''_CR, _CR, _EOS'' (13, 13, 255) que acaban nuestra cadena en el código:+Después de ese ''RET'' vienen los datos (podemos ver la cadena de texto) hasta el final de la línea 00000060 y principio de 00000070, donde vemos **$0d $0d $ff**. Estos son los ''_CR, _CR, _EOS'' (13, 13, 255) que acaban nuestra cadena en el código:
  
 <code z80> <code z80>
Línea 340: Línea 340:
 </code> </code>
  
-Todo el código desde después de dicho $FF hasta el final del binario es el código ensamblado de nuestra librería ''utils.asm'' que hemos añadido en ese punto con ''INCLUDE''. Efectivamente, al incluir nuestra librería estamos engordando el binario resultante con todo el código de la misma, por lo que lo habitual cuando se va a compilar la versión final y definitiva de un juego o programa es eliminar de las librerías todas aquellas funciones y funcionalidades que no se usan en el programa principal, haciendo que ocupe menos al ensamblarlo (y tarde menos en cargar desde cinta), o utilizar otros métodos del ensamblador, como ''IFUSED'' para que sólo se ensamble aquello que haya sido utilizado.+Todo el código desde después de dicho $ff hasta el final del binario es el código ensamblado de nuestra librería ''utils.asm'' que hemos añadido en ese punto con ''INCLUDE''. Efectivamente, al incluir nuestra librería estamos engordando el binario resultante con todo el código de la misma, por lo que lo habitual cuando se va a compilar la versión final y definitiva de un juego o programa es eliminar de las librerías todas aquellas funciones y funcionalidades que no se usan en el programa principal, haciendo que ocupe menos al ensamblarlo (y tarde menos en cargar desde cinta), o utilizar otros métodos del ensamblador, como ''IFUSED'' para que sólo se ensamble aquello que haya sido utilizado.
  
-Volvamos de nuevo a las etiquetas y a DB: Cuando en el programa hacemos ''LD HL, datos'', el ensamblador transforma esa instrucción en realidad en ''LD HL, 50066''. Gracias a esto podemos manipular los datos (que están en memoria) y leerlos y cambiarlos, utilizando un nombre como referencia a la celdilla de memoria de inicio de los mismos.+Volvamos de nuevo a las etiquetas y a DB: Cuando en el programa hacemos ''ld hl, datos'', el ensamblador transforma esa instrucción en realidad en ''ld hl, 50066''. Gracias a esto podemos manipular los datos (que están en memoria) y leerlos y cambiarlos, utilizando un nombre como referencia a la celdilla de memoria de inicio de los mismos.
  
  Lo mismo ocurre con el texto que se ha definido entre dobles comillas. A partir de la dirección definida por ''texto'' se colocan todos los bytes que forman la cadena ''Esto es una cadena de texto''. Cada byte en memoria es una letra de la cadena, en formato ASCII (La "E" es $45, la "s" es $73", etc.).  Lo mismo ocurre con el texto que se ha definido entre dobles comillas. A partir de la dirección definida por ''texto'' se colocan todos los bytes que forman la cadena ''Esto es una cadena de texto''. Cada byte en memoria es una letra de la cadena, en formato ASCII (La "E" es $45, la "s" es $73", etc.).
Línea 368: Línea 368:
     (...)     (...)
  
-    LD A, (vidas)+    ld a, (vidas)
     (...)     (...)
  
 muerte: muerte:
-    DEC A +    dec a 
-    LD (vidas), A+    ld (vidas), a
 </code> </code>
  
Línea 393: Línea 393:
     datos DB 00, 201, 100, 12, 255, 11     datos DB 00, 201, 100, 12, 255, 11
  
-    LD BA+    ld ba
     (más instrucciones)     (más instrucciones)
-    RET+    ret
  
     END 50000     END 50000
Línea 405: Línea 405:
     ORG 50000     ORG 50000
  
-    ; Ahora el salto a 50000 ejecutará el LD BA, no los+    ; Ahora el salto a 50000 ejecutará el ld ba, no los
     ; datos que habíamos introducido antes.     ; datos que habíamos introducido antes.
-    LD BA+    ld ba
  
     (más instrucciones)     (más instrucciones)
  
-    RET+    ret
  
-; Aquí nunca serán ejecutados, el RET está antes.+; Aquí nunca serán ejecutados, el ret está antes.
 datos DB 00, 201, 100, 12, 255, 11 datos DB 00, 201, 100, 12, 255, 11
  
Línea 430: Línea 430:
  Para empezar vamos a ver 2 instrucciones de salto incondicionales, es decir, cuando lleguemos a una de esas 2 instrucciones, se modificará el registro PC para cambiar la ejecución del programa. De esta forma podremos realizar bucles, saltos a rutinas o funciones, etc.  Para empezar vamos a ver 2 instrucciones de salto incondicionales, es decir, cuando lleguemos a una de esas 2 instrucciones, se modificará el registro PC para cambiar la ejecución del programa. De esta forma podremos realizar bucles, saltos a rutinas o funciones, etc.
  
- Empecemos con ''JP'' (abreviatura de **JumP**):+ Empecemos con ''jp'' (abreviatura de **JumP**):
  
 <code z80> <code z80>
Línea 436: Línea 436:
     ORG 50000     ORG 50000
  
-    XOR A               ; A = 0+    xor a               ; A = 0
 bucle: bucle:
-    INC A               ; A = A + 1 +    inc a               ; A = A + 1 
-    LD (16384),       ; Escribir valor de A en (16384) +    ld (16384),       ; Escribir valor de A en (16384) 
-    JP bucle+    jp bucle
  
-    RET                 ; Esto nunca se ejecutará+    ret                 ; Esto nunca se ejecutará
  
     END 50000     END 50000
Línea 449: Línea 449:
  ¿Qué hace el ejemplo anterior? Ensamblémoslo con ''pasmo <nowiki>--</nowiki>tapbas bucle.asm bucle.tap'' y carguémoslo en BASIC.  ¿Qué hace el ejemplo anterior? Ensamblémoslo con ''pasmo <nowiki>--</nowiki>tapbas bucle.asm bucle.tap'' y carguémoslo en BASIC.
  
- Nada más entrar en 50000, se ejecuta un ''INC A''. Después se hace un ''LD (16384), A'', es decir, escribimos en la celdilla (16384) de la memoria el valor que contiene A. Esta celdilla se corresponde con los primeros 8 píxeles de la pantalla, con lo cual estaremos cambiando el contenido de la misma.+ Nada más entrar en 50000, se ejecuta un ''inc a''. Después se hace un ''ld (16384), a'', es decir, escribimos en la celdilla (16384) de la memoria el valor que contiene A. Esta celdilla se corresponde con los primeros 8 píxeles de la pantalla, con lo cual estaremos cambiando el contenido de la misma.
  
- Tras esta escritura, encontramos un ''JP bucle'', que lo que hace es cambiar el valor de PC y hacerlo, de nuevo, PC=50000. El código se volverá a repetir, y de nuevo al llegar a ''JP'' volveremos a saltar a la dirección definida por la etiqueta ''bucle''. Es un bucle infinito, realizado gracias a este salto incondicional (podemos reiniciar el Spectrum para retomar el control). Estaremos repitiendo una y otra vez la misma porción de código, que cambia el contenido de los 8 primeros píxeles de pantalla poniendo en ellos el valor de A (que varía  desde 0 a 255 continuadamente).+ Tras esta escritura, encontramos un ''jp bucle'', que lo que hace es cambiar el valor de PC y hacerlo, de nuevo, PC=50000. El código se volverá a repetir, y de nuevo al llegar a ''jp'' volveremos a saltar a la dirección definida por la etiqueta ''bucle''. Es un bucle infinito, realizado gracias a este salto incondicional (podemos reiniciar el Spectrum para retomar el control). Estaremos repitiendo una y otra vez la misma porción de código, que cambia el contenido de los 8 primeros píxeles de pantalla poniendo en ellos el valor de A (que varía  desde 0 a 255 continuadamente).
  
- //Utilizaremos pues JP para cambiar el rumbo del programa// y cambiar PC para ejecutar otras porciones de código (anteriores o posteriores a la posición actual) del mismo. ''JP'' realiza pues lo que se conoce como "SALTO INCONDICIONAL ABSOLUTO", es decir, saltar a una posición absoluta de memoria (una celdilla de 0 a 65535), mediante la asignación de dicho valor al registro PC.+ //Utilizaremos pues jp para cambiar el rumbo del programa// y cambiar PC para ejecutar otras porciones de código (anteriores o posteriores a la posición actual) del mismo. ''jp'' realiza pues lo que se conoce como "SALTO INCONDICIONAL ABSOLUTO", es decir, saltar a una posición absoluta de memoria (una celdilla de 0 a 65535), mediante la asignación de dicho valor al registro PC.
  
- Existen 3 maneras de usar ''JP'':+ Existen 3 maneras de usar ''jp'':
  
 \\  \\ 
-a.- **JP NN**:+a.- **jp NN**:
  
 Saltar a la dirección NN. Literalmente: PC = NN Saltar a la dirección NN. Literalmente: PC = NN
Línea 464: Línea 464:
 \\  \\ 
 \\  \\ 
-b.- **JP (HL)**+b.- **jp (hl)**
  
 Saltar a la dirección contenida en el registro HL (ojo, no a la dirección apuntada por el registro HL, sino directamente a su valor). Literalmente: PC = HL Saltar a la dirección contenida en el registro HL (ojo, no a la dirección apuntada por el registro HL, sino directamente a su valor). Literalmente: PC = HL
Línea 470: Línea 470:
 \\  \\ 
 \\  \\ 
-c.- **JP (registro_indice)**+c.- **jp (registro_indice)**
  
 Saltar a la dirección contenida en IX o IY. Literalmente: PC = IX o PC = IY Saltar a la dirección contenida en IX o IY. Literalmente: PC = IX o PC = IY
Línea 481: Línea 481:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- JP NN               |- - - - - -| + jp NN               |- - - - - -| 
- JP (HL)             |- - - - - -| + jp (hl)             |- - - - - -| 
- JP (IX)             |- - - - - -| + jp (ix)             |- - - - - -| 
- JP (IY)             |- - - - - -|+ jp (iy)             |- - - - - -|
 </code> </code>
  
- Recordemos que nuestra CPU almacena primero en memoria los bytes bajos de los números de 16 bits, por lo que a la hora de ensamblar un salto como ''JP 50000'' (''JP $C350''), dicha instrucción será traducida como:+ Recordemos que nuestra CPU almacena primero en memoria los bytes bajos de los números de 16 bits, por lo que a la hora de ensamblar un salto como ''jp 50000'' (''jp $c350''), dicha instrucción será traducida como:
  
 <code> <code>
Línea 496: Línea 496:
  
 <code> <code>
-JP 50 C3  ->  JP $C350  ->  JP 50000+jp 50 C3  ->  jp $c350  ->  jp 50000
 </code> </code>
  
  Como podéis ver, aparte del código de instrucción (C3) almacenamos un valor numérico, absoluto, de la posición a la que saltar. Es pues una instrucción de 3 bytes.  Como podéis ver, aparte del código de instrucción (C3) almacenamos un valor numérico, absoluto, de la posición a la que saltar. Es pues una instrucción de 3 bytes.
  
- Literalmente, **JP NN** se traduce por **PC=NN** .+ Literalmente, **jp NN** se traduce por **PC=NN** .
  
  
Línea 507: Línea 507:
 ===== Saltos relativos incondicionales: JR ===== ===== Saltos relativos incondicionales: JR =====
  
- Además de ''JP'', tenemos otra instrucción para realizar saltos incondicionales: ''JR''JR trabaja exactamente igual que JP: realiza un salto (cambiando el valor del registro PC), pero lo hace de forma diferente.+ Además de ''jp'', tenemos otra instrucción para realizar saltos incondicionales: ''jr''jr trabaja exactamente igual que jp: realiza un salto (cambiando el valor del registro PC), pero lo hace de forma diferente.
  
- **JR** son las siglas de "**Jump Relative**", y es que esta instrucción en lugar de realizar un salto absoluto (a una posición de memoria+ **jr** son las siglas de "**Jump Relative**", y es que esta instrucción en lugar de realizar un salto absoluto (a una posición de memoria
 0-65535), lo hace //de forma relativa//, es decir, a una posición de memoria alrededor de la posición actual (una vez decodificada la 0-65535), lo hace //de forma relativa//, es decir, a una posición de memoria alrededor de la posición actual (una vez decodificada la
-instrucción JR).+instrucción jr).
  
- El argumento de ''JR'' no es pues un valor numérico de 16 bits (0-65535) sino un valor de 8 bits en complemento a dos que nos permite saltar desde la posición actual (referenciada en el ensamblador como "$") hasta 127 bytes hacia adelante y 127 bytes hacia atrás:+ El argumento de ''jr'' no es pues un valor numérico de 16 bits (0-65535) sino un valor de 8 bits en complemento a dos que nos permite saltar desde la posición actual (referenciada en el ensamblador como "$") hasta 127 bytes hacia adelante y 127 bytes hacia atrás:
  
- Ejemplos de instrucciones JR:+ Ejemplos de instrucciones jr:
  
 <code> <code>
-JR $+25      ; Saltar adelante 25 bytes: PC = PC+25 +jr $+25      ; Saltar adelante 25 bytes: PC = PC+25 
-JR $-100     ; Saltar atrás 100 bytes:   PC = PC-100+jr $-100     ; Saltar atrás 100 bytes:   PC = PC-100
 </code> </code>
  
Línea 532: Línea 532:
  
 bucle: bucle:
-    INC A +    inc a 
-    LD (16384), A +    ld (16384), a 
-    JR bucle+    jr bucle
  
-    RET ; Esto nunca se ejecutará+    ret ; Esto nunca se ejecutará
  
     END     END
Línea 545: Línea 545:
 ensamblarlo. ensamblarlo.
  
- ¿Qué diferencia tiene JP con JR? Pues bien: para empezar en lugar de ocupar 3 bytes (JP + la dirección de 16 bits), ocupa sólo 2 (JR ++ ¿Qué diferencia tiene jp con jr? Pues bien: para empezar en lugar de ocupar 3 bytes (jp + la dirección de 16 bits), ocupa sólo 2 (jr +
 el desplazamiento de 8 bits) con lo cual se decodifica y ejecuta más rápido. el desplazamiento de 8 bits) con lo cual se decodifica y ejecuta más rápido.
  
Línea 556: Línea 556:
 </code> </code>
  
- En ese caso, tendremos que cambiar la instrucción ''JR etiqueta'' por un ''JP etiqueta'', de forma que el ensamblador utilice un salto absoluto que le permita llegar a la posición de memoria que queremos saltar y que está más alejada de que la capacidad de salto de JR. Se podría decir que "hay demasiado código" entre el punto de salto y el destino del salto, más de 127 bytes, y por tanto necesitamos hacer un salto absoluto.+ En ese caso, tendremos que cambiar la instrucción ''jr etiqueta'' por un ''jp etiqueta'', de forma que el ensamblador utilice un salto absoluto que le permita llegar a la posición de memoria que queremos saltar y que está más alejada de que la capacidad de salto de jr. Se podría decir que "hay demasiado código" entre el punto de salto y el destino del salto, más de 127 bytes, y por tanto necesitamos hacer un salto absoluto.
  
- ¿Cuál es la utilidad o ventaja de los saltos relativos aparte de ocupar 2 bytes en lugar de 3? Pues que los saltos realizados en rutinas que usen ''JR'' y no ''JP'' son todos relativos a la posición actual, con lo cual la rutina es **REUBICABLE**. Es decir, si cambiamos nuestra rutina de 50000 a 60000 (por ejemplo), funcionará, porque los saltos son relativos a "$". En una rutina programada con ''JP'', si la pokeamos en 60000 en lugar de en 50000 y la hemos ensanmblado con ''ORG 50000'', cuando hagamos saltos (''JP 50003'', por ejemplo), saltaremos a lugares donde no está el código (ahora está en 60003) y el programa no hará lo que esperamos. En resumen: ''JR'' permite programar rutinas reubicables y ''JP'' no.+ ¿Cuál es la utilidad o ventaja de los saltos relativos aparte de ocupar 2 bytes en lugar de 3? Pues que los saltos realizados en rutinas que usen ''jr'' y no ''jp'' son todos relativos a la posición actual, con lo cual la rutina es **REUBICABLE**. Es decir, si cambiamos nuestra rutina de 50000 a 60000 (por ejemplo), funcionará, porque los saltos son relativos a "$". En una rutina programada con ''jp'', si la pokeamos en 60000 en lugar de en 50000 y la hemos ensanmblado con ''ORG 50000'', cuando hagamos saltos (''jp 50003'', por ejemplo), saltaremos a lugares donde no está el código (ahora está en 60003) y el programa no hará lo que esperamos. En resumen: ''jr'' permite programar rutinas reubicables y ''jp'' no.
  
 Se dice que una rutina es reubicable cuando estando programada a partir de una determinada dirección de memoria, podemos copiar una rutina ya ensamblada a otra dirección y sus saltos funcionarán correctamente por no ser absolutos. Se dice que una rutina es reubicable cuando estando programada a partir de una determinada dirección de memoria, podemos copiar una rutina ya ensamblada a otra dirección y sus saltos funcionarán correctamente por no ser absolutos.
Línea 568: Línea 568:
  Nuestro ensamblador (Pasmo, z80asm, sjasmplus, etc) nos permite utilizar etiquetas, que serán reemplazadas por sus direcciones de memoria durante el proceso de ensamblado. Nosotros podemos modificar las posibles de nuestras rutinas en el código, y dejar que el ensamblador las "reubique" por nosotros, ya que al ensamblará cambiará todas las referencias a las etiquetas que usamos.  Nuestro ensamblador (Pasmo, z80asm, sjasmplus, etc) nos permite utilizar etiquetas, que serán reemplazadas por sus direcciones de memoria durante el proceso de ensamblado. Nosotros podemos modificar las posibles de nuestras rutinas en el código, y dejar que el ensamblador las "reubique" por nosotros, ya que al ensamblará cambiará todas las referencias a las etiquetas que usamos.
  
- Esta facilidad de trabajo contrasta con las dificultades que tenían los programadores de la época que no disponían de ensambladores profesionales. Imaginad la cantidad de usuarios que ensamblaban sus programas a mano, usando saltos relativos y absolutos (y como veremos, llamadas a subrutinas), que en lugar de sencillos nombres (''JP A_mayor_que_B'') utilizaban directamente direcciones en memoria.+ Esta facilidad de trabajo contrasta con las dificultades que tenían los programadores de la época que no disponían de ensambladores profesionales. Imaginad la cantidad de usuarios que ensamblaban sus programas a mano, usando saltos relativos y absolutos (y como veremos, llamadas a subrutinas), que en lugar de sencillos nombres (''jp A_mayor_que_B'') utilizaban directamente direcciones en memoria.
  
  E imaginad el trabajo que suponía mantener un listado en papel todas los direcciones de saltos, subrutinas y variables, referenciados por direcciones de memoria y no por nombres, y tener que cambiar muchos de ellos cada vez que tenían que arreglar un fallo en una subrutina y cambiaban los destinos de los saltos por crecer el código que había entre ellos.  E imaginad el trabajo que suponía mantener un listado en papel todas los direcciones de saltos, subrutinas y variables, referenciados por direcciones de memoria y no por nombres, y tener que cambiar muchos de ellos cada vez que tenían que arreglar un fallo en una subrutina y cambiaban los destinos de los saltos por crecer el código que había entre ellos.
  
- Dejando ese tema aparte, la tabla de afectación de flags de JR es la misma que para JP: nula.+ Dejando ese tema aparte, la tabla de afectación de flags de jr es la misma que para jp: nula.
  
 <code> <code>
Línea 578: Línea 578:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
-  JR d               |- - - - - -|+  jr d               |- - - - - -|
  
  (Donde "d" es un desplazamiento de 8 bits)  (Donde "d" es un desplazamiento de 8 bits)
Línea 584: Línea 584:
  
  
- Literalmente, ''JR d'' se traduce por PC=PC+d.+ Literalmente, ''jr d'' se traduce por PC=PC+d.
  
  
Línea 595: Línea 595:
  
 \\  \\ 
- **JP NZ, direccion** : Salta si el indicador de cero (Z) está a cero (resultado no cero).\\  + **jp nz, direccion** : Salta si el indicador de cero (Z) está a cero (resultado no cero).\\  
- **JP Z, direccion**  : Salta si el indicador de cero (Z) está a uno (resultado cero).\\  + **jp z, direccion**  : Salta si el indicador de cero (Z) está a uno (resultado cero).\\  
- **JP NC, direccion** : Salta si el indicador de carry (C) está a cero.\\  + **jp nc, direccion** : Salta si el indicador de carry (C) está a cero.\\  
- **JP C, direccion**  : Salta si el indicador de carry (C) está a uno.\\  + **jp c, direccion**  : Salta si el indicador de carry (C) está a uno.\\  
- **JP PO, direccion** : Salta si el indicador de paridad/desbordamiento (P/O) está a cero.\\  + **jp po, direccion** : Salta si el indicador de paridad/desbordamiento (P/O) está a cero.\\  
- **JP PE, direccion** : Salta si el indicador de paridad/desbordamiento (P/O) está a uno.\\  + **jp pe, direccion** : Salta si el indicador de paridad/desbordamiento (P/O) está a uno.\\  
- **JP P, direccion**  : Salta si el indicador de signo S está a cero (resultado positivo).\\  + **jp p, direccion**  : Salta si el indicador de signo S está a cero (resultado positivo).\\  
- **JP M, direccion**  : Salta si el indicador de signo S está a uno (resultado negativo).\\ + **jp m, direccion**  : Salta si el indicador de signo S está a uno (resultado negativo).\\ 
 \\  \\ 
- **JR NZ, relativo** : Salta si el indicador de cero (Z) está a cero (resultado no cero).\\  + **jr nz, relativo** : Salta si el indicador de cero (Z) está a cero (resultado no cero).\\  
- **JR Z,  relativo** : Salta si el indicador de cero (Z) está a uno (resultado cero).\\  + **jr z,  relativo** : Salta si el indicador de cero (Z) está a uno (resultado cero).\\  
- **JR NC, relativo** : Salta si el indicador de carry (C) está a cero.\\  + **jr nc, relativo** : Salta si el indicador de carry (C) está a cero.\\  
- **JR C,  relativo** : Salta si el indicador de carry (C) está a uno.\\ + **jr c,  relativo** : Salta si el indicador de carry (C) está a uno.\\ 
 \\  \\ 
  
Línea 617: Línea 617:
  
 <code z80> <code z80>
-    JP Z, destino +    jp z, destino 
-    LD A, 10+    ld a, 10
 destino: destino:
-    NOP+    nop
 </code> </code>
  
  (donde "destino" es una etiqueta definida en algún lugar de nuestro programa, aunque también habríamos podido especificar directamente una dirección como por ejemplo 50004).  (donde "destino" es una etiqueta definida en algún lugar de nuestro programa, aunque también habríamos podido especificar directamente una dirección como por ejemplo 50004).
  
- Cuando el procesador lee el ''JP Z, destino'', lo que hace es lo siguiente:+ Cuando el procesador lee el ''jp z, destino'', lo que hace es lo siguiente:
  
-   * Si el flag Z está activado (a uno), saltamos a "destino" (con lo cual no se ejecuta el ''LD A, 10''), ejecutándose el código a partir del ''NOP''+   * Si el flag Z está activado (a uno), saltamos a "destino" (con lo cual no se ejecuta el ''ld a, 10''), ejecutándose el código a partir del ''NOP''
-   * Si no está activo (a cero) no se realiza ningún salto, con lo que se ejecutaría el ''LD A, 10'', y seguiría después con el ''NOP''.+   * Si no está activo (a cero) no se realiza ningún salto, con lo que se ejecutaría el ''ld a, 10'', y seguiría después con el ''NOP''.
  
- En BASIC, ''JP Z, destino'' sería algo como:+ En BASIC, ''jp z, destino'' sería algo como:
  
 <code basic> <code basic>
Línea 636: Línea 636:
 </code> </code>
  
- Y ''JP NZ, destino'' sería:+ Y ''jp nz, destino'' sería:
  
 <code basic> <code basic>
Línea 648: Línea 648:
 <code z80> <code z80>
  
-    ; Repetir 100 veces la instruccion NOP +    ; Repetir 100 veces la instruccion nop 
-    LD A, 100+    ld a, 100
 bucle: bucle:
-    NOP+    nop
  
-    DEC A          ; Decrementamos A. +    dec a               ; Decrementamos A. 
-                   ; Cuando A sea cero, Z se pondrá a 1+                        ; Cuando A sea cero, Z se pondrá a 1
  
-    JR NZ, bucle   ; Mientras Z=0, repetir el bucle+    jr nz, bucle        ; Mientras Z=0, repetir el bucle 
 + 
 +    ld a, 200           ; Aquí llegaremos cuando Z sea 1 (A valga 0)
  
-    LD A, 200      ; Aquí llegaremos cuando Z sea 1 (A valga 0) 
     ; resto del programa     ; resto del programa
 </code> </code>
  
- Es decir: cargamos en A el valor 100, y tras ejecutar la instrucción ''NOP'' hacemos un ''DEC A'' que decrementa su valor (a 99). Como el resultado de ''DEC A'' es 99 y no cero, el flag de Z (de cero) se queda a 0, (recordemos que sólo se pone a uno cuando la última operación resultó ser cero).+ Es decir: cargamos en A el valor 100, y tras ejecutar la instrucción ''NOP'' hacemos un ''dec a'' que decrementa su valor (a 99). Como el resultado de ''dec a'' es 99 y no cero, el flag de Z (de cero) se queda a 0, (recordemos que sólo se pone a uno cuando la última operación resultó ser cero).
  
- Y como el flag Z es cero (NON ZERO = no activado el flag zero) la instrucción ''JR NZ, bucle'' realiza un salto a la etiqueta "bucle". Allí se ejecuta el NOP y de nuevo el ''DEC A'', dejando ahora A en 98.+ Y como el flag Z es cero (NON ZERO = no activado el flag zero) la instrucción ''jr nz, bucle'' realiza un salto a la etiqueta "bucle". Allí se ejecuta el nop y de nuevo el ''dec a'', dejando ahora A en 98.
  
- Tras repetirse 100 veces el proceso, llegará un momento en que A valga cero tras el ''DEC A''. En ese momento se activará el flag de ZERO con lo que la instrucción ''JR NZ, bucle'' no realizará el salto y continuará con el ''LD A, 20''.+ Tras repetirse 100 veces el proceso, llegará un momento en que A valga cero tras el ''dec a''. En ese momento se activará el flag de ZERO con lo que la instrucción ''jr nz, bucle'' no realizará el salto y continuará con el ''ld a, 20''.
  
  Así pues, **acabamos implementar un bucle gracias a los flags y las instrucciones condicionales**.  Así pues, **acabamos implementar un bucle gracias a los flags y las instrucciones condicionales**.
Línea 679: Línea 680:
  
 <code z80> <code z80>
-    SUB B              ; A = A-B +    sub b                ; A = A-B 
-    JR Z, iguales      ; Si Z=1 saltar a iguales +    jr z, iguales        ; Si Z=1 saltar a iguales 
-    JR NZ, distintos   ; Si Z=0 saltar a distintos+    jr nz, distintos     ; Si Z=0 saltar a distintos
  
 iguales: iguales:
     ;;; (código)     ;;; (código)
-    JR seguir+    jr seguir
  
 distintos: distintos:
     ;;; (código)     ;;; (código)
-    ;JR seguir         ; Este salto no es necesario, +    ;jr seguir           ; Este salto no es necesario, 
-                       ; ya continuamos en "seguir"+                         ; ya continuamos en "seguir"
  
 seguir: seguir:
Línea 698: Línea 699:
  
 <code z80> <code z80>
-    SUB B              ; A = A-B +    sub b                ; A = A-B 
-    JR NZ, distintos   ; Si Z=0 saltar a distintos +    jr nz, distintos     ; Si Z=0 saltar a distintos 
-                       ; Si Z=1, seguimos aqui, ya en "iguales"+                         ; Si Z=1, seguimos aqui, ya en "iguales"
  
 iguales: iguales:
     ;;; (código)     ;;; (código)
-    JR seguir+    jr seguir
  
 distintos: distintos:
-    ;;; (código)       ; No es neces+    ;;; (código)         ; No es necesario el salto
  
 seguir: seguir:
 </code> </code>
  
- (Nota: se podría haber usado ''JP'' en vez de ''JR'').+ (Nota: se podría haber usado ''jp'' en vez de ''jr'').
  
- Para comparar A con B los restamos (A=A-B). Si el resultado de la resta es cero, es porque A era igual a B. Si no es cero, es que eran distintos. Y utilizando el flag de Zero con ''JP Z'' y ''JP NZ'' podemos detectar esa diferencia.+ Para comparar A con B los restamos (A=A-B). Si el resultado de la resta es cero, es porque A era igual a B. Si no es cero, es que eran distintos. Y utilizando el flag de Zero con ''jp Z'' y ''jp NZ'' podemos detectar esa diferencia.
  
  Pronto veremos más a fondo otras instrucciones de comparación para evitar hacer la resta y perder el valor de A, pero este ejemplo debe bastar para demostrar la importancia de los flags y de su uso en instrucciones de salto condicionales. Bien utilizadas podemos alterar el flujo del programa a voluntad. Es cierto que no es tan inmediato ni cómodo como los >, <, = y <> de BASIC, pero el resultado es el mismo, y es fácil acostumbrarse a este tipo de comparaciones mediante el estado de los flags.  Pronto veremos más a fondo otras instrucciones de comparación para evitar hacer la resta y perder el valor de A, pero este ejemplo debe bastar para demostrar la importancia de los flags y de su uso en instrucciones de salto condicionales. Bien utilizadas podemos alterar el flujo del programa a voluntad. Es cierto que no es tan inmediato ni cómodo como los >, <, = y <> de BASIC, pero el resultado es el mismo, y es fácil acostumbrarse a este tipo de comparaciones mediante el estado de los flags.
  
- Para finalizar, un detalle sobre **DEC''+''JR**: La combinación **DEC B'' / ''JR NZ** se puede sustituir (es más eficiente, y más sencillo) por el comando **DJNZ**, que literalmente significa "Decrementa B y si no es cero, salta a <direccion>".+ Para finalizar, un detalle sobre **DEC''+''jr**: La combinación **dec b'' / ''jr NZ** se puede sustituir (es más eficiente, y más sencillo) por el comando **djnz**, que literalmente significa "Decrementa B y si no es cero, salta a <direccion>".
  
 \\  \\ 
- **DJNZ direccion**+ **djnz direccion**
  
  Equivale a decrementar B y a la dirección indicada en caso de que B no valga cero tras el decremento.  Equivale a decrementar B y a la dirección indicada en caso de que B no valga cero tras el decremento.
Línea 729: Línea 730:
      ; (pasos a repertir)      ; (pasos a repertir)
  
-    DJNZ bucle                 ; Decrementar B, salta si distinto de 0+    djnz bucle                 ; Decrementar B, salta si distinto de 0
 </code> </code>
  
- Esta instrucción se usa habitualmente en bucles (usando B como iterador del mismo) y, al igual que JP JR, no afecta al estado de los flags:+ Esta instrucción se usa habitualmente en bucles (usando B como iterador del mismo) y, al igual que jp jr, no afecta al estado de los flags:
  
 <code> <code>
Línea 738: Línea 739:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
-  |JP COND, NN       |- - - - - -| +  |jp COND, NN       |- - - - - -| 
-  |JR COND, d        |- - - - - -| +  |jr COND, d        |- - - - - -| 
-  |DJNZ d            |- - - - - -|+  |djnz d            |- - - - - -|
 </code> </code>
  
- El argumento de salto de ''DJNZ'' es de 1 byte, por lo que para saltos relativos de más de 127 bytes hacia atrás o hacia adelante (-127 a +127), DJNZ se tiene que sustituir por la siguiente combinación de instrucciones:+ El argumento de salto de ''DJNZ'' es de 1 byte, por lo que para saltos relativos de más de 127 bytes hacia atrás o hacia adelante (-127 a +127), djnz se tiene que sustituir por la siguiente combinación de instrucciones:
  
 <code z80> <code z80>
Línea 749: Línea 750:
      ; (pasos a repertir)      ; (pasos a repertir)
  
-    DEC B                      ; Decrementar B, afecta a los flags +    dec b                      ; Decrementar B, afecta a los flags 
-    JP NZ, direccion           ; Salto absoluto: permite cualquier distancia+    jp nz, direccion           ; Salto absoluto: permite cualquier distancia
 </code> </code>
  
Línea 759: Línea 760:
     ; (pasos a repetir)     ; (pasos a repetir)
  
-    DEC BC                    ; Decrementamos BC -> no afecta a los flags +    dec bc                    ; Decrementamos BC -> no afecta a los flags 
-    LD A                  ; Cargamos B en A +    ld a                  ; Cargamos B en A 
-    OR C                      ; Hacemos OR a de A y C (de B y C) +    or c                      ; Hacemos OR a de A y C (de B y C) 
-    JR NZ, bucle              ; Si (B OR C) no es cero, BC != 0, saltar+    jr nz, bucle              ; Si (B or c) no es cero, BC != 0, saltar
 </code> </code>
  
Línea 774: Línea 775:
  
 <code z80> <code z80>
-CP origen+cp origen
 </code> </code>
  
Línea 784: Línea 785:
  
 <code z80> <code z80>
-    SUB B                  ; A = A-B +    sub b                    ; A = A-B 
-    JR Z, iguales          ; Si Z=1 saltar a iguales +    jr z, iguales            ; Si Z=1 saltar a iguales 
-    JR NZ, distintos       ; Si Z=0 saltar a distintos+    jr nz, distintos         ; Si Z=0 saltar a distintos
 </code> </code>
  
Línea 792: Línea 793:
  
 <code z80> <code z80>
-    CP B                   ; Flags = estado(A-B) +    cp b                     ; Flags = estado(A-B) 
-    JR Z, iguales          ; Si Z=1 saltar a iguales +    jr z, iguales            ; Si Z=1 saltar a iguales 
-    JR NZ, distintos       ; Si Z=0 saltar a distintos+    jr nz, distintos         ; Si Z=0 saltar a distintos
 </code> </code>
  
Línea 801: Línea 802:
 <code z80> <code z80>
     ; Comparación entre A Y B (=, > y <)     ; Comparación entre A Y B (=, > y <)
-    LD B, 5 +    ld b, 5 
-    LD A, 3+    ld a, 3
  
-    CP B                            ; Flags = estado(A-B) +    cp b                            ; Flags = estado(A-B) 
-    JP Z, A_Igual_que_B             ; IF(a-b)=0 THEN a=b +    jp z, A_Igual_que_B             ; IF(a-b)=0 THEN a=b 
-    JP NC, A_Mayor_o_igual_que_B    ; IF(a-b)>0 THEN a>=b +    jp nc, A_Mayor_o_igual_que_B    ; IF(a-b)>0 THEN a>=b 
-    JP C, A_Menor_que_B             ; IF(a-b)<0 THEN a<b+    jp c, A_Menor_que_B             ; IF(a-b)<0 THEN a<b
  
 A_Mayor_que_B: A_Mayor_que_B:
     ;;; (instrucciones)     ;;; (instrucciones)
-    JP fin+    jp fin
  
 A_Menor_que_B: A_Menor_que_B:
     ;;; (instrucciones)     ;;; (instrucciones)
-    JP fin+    jp fin
  
 A_Igual_que_B: A_Igual_que_B:
Línea 827: Línea 828:
  
 <code z80> <code z80>
-    LD B, 5 +    ld b, 5 
-    LD A, 3 +    ld a, 3 
-    LD C, 6+    ld c, 6
  
-    CP B                  ; IF A==B +    cp b                    ; IF A==B 
-    JR Z, A_Igual_a_B     ; THEN goto A_Igual_a_B +    jr z, A_Igual_a_B       ; THEN goto A_Igual_a_B 
-    CP C                  ; IF A==C +    cp c                    ; IF A==C 
-    JR Z, A_Igual_a_C     ; THEN goto A_Igual_a_C +    jr z, A_Igual_a_C       ; THEN goto A_Igual_a_C 
-    JP Fin                ; si no, salimos+    jp Fin                  ; si no, salimos
  
 A_Igual_a_B: A_Igual_a_B:
     ;;; (...)     ;;; (...)
-    JR Fin+    jr Fin
  
 A_Igual_a_C: A_Igual_a_C:
Línea 854: Línea 855:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- |CP s               |* * * V 1 *|+ |cp s               |* * * V 1 *|
 </code> </code>
  
Línea 868: Línea 869:
 <code z80> <code z80>
     ;;; Comparacion 16 bits de HL y DE     ;;; Comparacion 16 bits de HL y DE
-    LD AH +    ld ah 
-    CP D +    cp d 
-    JR NZ, no_iguales +    jr nz, no_iguales 
-    LD AL +    ld al 
-    CP E +    cp e 
-    JR NZ, no_iguales+    jr nz, no_iguales 
 iguales: iguales:
     ;;; (...)     ;;; (...)
Línea 886: Línea 888:
     ;;; Comparacion 16 bits de HL y VALOR_NUMERICO (inmediato)     ;;; Comparacion 16 bits de HL y VALOR_NUMERICO (inmediato)
     ;;; VALOR_NUMERICO puede ser cualquier valor de 0 a 65535     ;;; VALOR_NUMERICO puede ser cualquier valor de 0 a 65535
-    LD AH +    ld ah 
-    CP VALOR_NUMERICO / 256         ; Parte alta (VALOR/256) +    cp VALOR_NUMERICO / 256         ; Parte alta (VALOR/256) 
-    JR NZ, no_iguales +    jr nz, no_iguales 
-    LD AL +    ld al 
-    CP VALOR_NUMERICO % 256         ; Parte baja (Resto de VALOR/256) +    cp VALOR_NUMERICO % 256         ; Parte baja (Resto de VALOR/256) 
-    JR NZ, no_iguales+    jr nz, no_iguales
 iguales: iguales:
     ;;; (...)     ;;; (...)
Línea 902: Línea 904:
 ===== Consideraciones de las condiciones ===== ===== Consideraciones de las condiciones =====
  
- A la hora de utilizar instrucciones condicionales hay que tener en cuenta que no todas las instrucciones afectan a los flags. Por ejemplo, la instrucción ''DEC BC'' no pondrá el flag Z a uno cuando BC sea cero. Si intentamos montar un bucle mediante ''DEC BC'' + ''JR NZ'', nunca saldremos del mismo, ya que ''DEC BC'' no afecta al flag de zero.+ A la hora de utilizar instrucciones condicionales hay que tener en cuenta que no todas las instrucciones afectan a los flags. Por ejemplo, la instrucción ''dec bc'' no pondrá el flag Z a uno cuando BC sea cero. Si intentamos montar un bucle mediante ''dec bc'' + ''jr nz'', nunca saldremos del mismo, ya que ''dec bc'' no afecta al flag de zero.
  
 <code z80> <code z80>
-    LD BC, 1000        ; BC = 1000+    ld bc, 1000        ; BC = 1000
 bucle: bucle:
     (...)     (...)
  
-    DEC BC             ; BC = BC-1 (pero NO ALTERA el Carry Flag) +    dec bc             ; BC = BC-1 (pero NO ALTERA el Carry Flag) 
-    JR NZ, bucle       ; Nunca se pondrá a uno el ZF, siempre salta+    jr nz, bucle       ; Nunca se pondrá a uno el ZF, siempre salta
 </code> </code>
  
Línea 918: Línea 920:
  
 <code z80> <code z80>
-    LD BC, 1000        ; BC = 1000+    ld bc, 1000        ; BC = 1000
  
 bucle: bucle:
     (...)     (...)
-    DEC BC             ; Decrementamos BC. No afecta a F. +    dec bc             ; Decrementamos BC. No afecta a F. 
-    LD A           ; A = B +    ld a           ; A = B 
-    OR C               ; A = A OR C+    or c               ; A = A or c
                        ; Esto sí que afecta a los flags.                        ; Esto sí que afecta a los flags.
                        ; Si B==C y ambos son cero, el resultado                        ; Si B==C y ambos son cero, el resultado
                        ; del OR será cero y el ZF se pondrá a 1.                        ; del OR será cero y el ZF se pondrá a 1.
-    JR NZ, bucle       ; ahora sí que funcionará el salto si BC=0+    jr nz, bucle       ; ahora sí que funcionará el salto si BC=0
 </code> </code>
  
- Más detalles sobre los saltos condicionales: esta vez respecto al signo. Las condiciones P y M (''JP P'', ''JP M'') nos permitirán realizar saltos según el estado del bit de signo. Resultará especialmente útil después de operaciones aritméticas.+ Más detalles sobre los saltos condicionales: esta vez respecto al signo. Las condiciones P y M (''jp p'', ''jp m'') nos permitirán realizar saltos según el estado del bit de signo. Resultará especialmente útil después de operaciones aritméticas.
  
- Los saltos por Paridad/Overflow (JP POJP PE) permitirán realizar saltos en función de la paridad cuando la última operación realizada modifique ese bit de F según la paridad del resultado. La misma condición nos servirá para desbordamientos si la última operación que afecta a flags realizada modifica este bit con respecto a dicha condición.+ Los saltos por Paridad/Overflow (jp pojp PE) permitirán realizar saltos en función de la paridad cuando la última operación realizada modifique ese bit de F según la paridad del resultado. La misma condición nos servirá para desbordamientos si la última operación que afecta a flags realizada modifica este bit con respecto a dicha condición.
  
- ¿Qué quiere decir esto? Que si, por ejemplo, realizamos una suma o resta, ''JP PO'' y ''JP PE'' responderán en función de si ha habido un desbordamiento o no y no en función de la paridad, porque las sumas y restas actualizan dicho flag según los desbordamientos, no según la paridad.+ ¿Qué quiere decir esto? Que si, por ejemplo, realizamos una suma o resta, ''jp po'' y ''jp pe'' responderán en función de si ha habido un desbordamiento o no y no en función de la paridad, porque las sumas y restas actualizan dicho flag según los desbordamientos, no según la paridad.
  
 \\  \\ 
Línea 951: Línea 953:
  
 <code z80> <code z80>
-    CP +    cp 
-    JR Z, es_cero +    jr z, es_cero 
-    LD A, 'y' +    ld a, 'y' 
-    JR continuar         ; Podemos evitarnos este salto+    jr continuar         ; Podemos evitarnos este salto 
 es_cero: es_cero:
-    LD A, 'x'+    ld a, 'x' 
 continuar: continuar:
     ;;; Aqui A vale 'x' o 'y' segun el valor de 0     ;;; Aqui A vale 'x' o 'y' segun el valor de 0
Línea 964: Línea 968:
  
 <code z80> <code z80>
-    CP +    cp 
-    LD A, 'x' +    ld a, 'x' 
-    JR Z, es_cero +    jr z, es_cero 
-    LD A, 'y'+    ld a, 'y' 
 es_cero: es_cero:
     ;;; Aqui A vale 'x' o 'y' segun el valor de 0     ;;; Aqui A vale 'x' o 'y' segun el valor de 0
Línea 977: Línea 982:
  Ante una instrucción condicional, el microprocesador tendrá 2 opciones, según los valores que comparemos y el tipo de comparación que hagamos (si es cero, si no es cero, si es mayor o menor, etc.). Al final, sólo habrá 2 caminos posibles: saltar a una dirección de destino, o no saltar y continuar en la dirección de memoria siguiente al salto condicional.  Ante una instrucción condicional, el microprocesador tendrá 2 opciones, según los valores que comparemos y el tipo de comparación que hagamos (si es cero, si no es cero, si es mayor o menor, etc.). Al final, sólo habrá 2 caminos posibles: saltar a una dirección de destino, o no saltar y continuar en la dirección de memoria siguiente al salto condicional.
  
- Aunque pueda parecer una pérdida de tiempo, en rutinas críticas //es muy interesante el pararse a pensar cuál puede ser el caso con más probabilidades de ejecución//, ya que el tiempo empleado en la opción "//CONDICION CIERTA, POR LO QUE SE PRODUCE EL SALTO//" es mayor que el empleado en "//CONDICION FALSA, NO SALTO Y SIGO//".+ Aunque pueda parecer una pérdida de tiempo, en rutinas críticas //es muy interesante el pararse a pensar cuál puede ser el caso con más probabilidades de ejecución//, ya que el tiempo empleado en la opción "//CONDICION CIERTA, Por lO QUE SE PRODUCE EL SALTO//" es mayor que el empleado en "//CONDICION FALSA, NO SALTO Y SIGO//".
  
- Por ejemplo, ante un ''JP Z, direccion'', el microprocesador tardará 10 ciclos de reloj en ejecutar un salto si la condición se cumple, y sólo 1 si no se cumple (ya que entonces no tiene que realizar salto alguno).+ Por ejemplo, ante un ''jp z, direccion'', el microprocesador tardará 10 ciclos de reloj en ejecutar un salto si la condición se cumple, y sólo 1 si no se cumple (ya que entonces no tiene que realizar salto alguno).
  
  Supongamos que tenemos una rutina crítica donde la velocidad es importante. Vamos a utilizar, como ejemplo, la siguiente rutina que devuelve 1 si el parámetro que le pasamos es mayor que 250 y devuelve 0 si es menor:  Supongamos que tenemos una rutina crítica donde la velocidad es importante. Vamos a utilizar, como ejemplo, la siguiente rutina que devuelve 1 si el parámetro que le pasamos es mayor que 250 y devuelve 0 si es menor:
Línea 990: Línea 995:
  
 Valor_Mayor_Que_250: Valor_Mayor_Que_250:
-    CP 250                      ; Comparamos A con 250 +    cp 250                      ; Comparamos A con 250 
-    JP C, A_menor_que_250       ; Si es menor, saltamos +    jp c, A_menor_que_250       ; Si es menor, saltamos 
-    LD A, 1                     ; si es mayor, devolvemos 1 +    ld a, 1                     ; si es mayor, devolvemos 1 
-    RET+    ret
  
 A_menor_que_250: A_menor_que_250:
-    LD A, 0 +    ld a, 0 
-    RET+    ret
 </code> </code>
  
Línea 1017: Línea 1022:
  
 Valor_Mayor_Que_250: Valor_Mayor_Que_250:
-    CP 250                      ; Comparamos A con 250 +    cp 250                      ; Comparamos A con 250 
-    JP NC, A_mayor_que_250      ; Si es mayor, saltamos +    jp nc, A_mayor_que_250      ; Si es mayor, saltamos 
-    LD A, 0                     ; si es menor, devolvemos 1 +    ld a, 0                     ; si es menor, devolvemos 1 
-    RET+    ret
  
 A_mayor_que_250: A_mayor_que_250:
-    LD A, 1 +    ld a, 1 
-    RET+    ret
 </code> </code>
  
Línea 1037: Línea 1042:
  
 <code> <code>
-FOR I=1 TO 20+For i=1 TO 20
    (código a repetir 20 veces)    (código a repetir 20 veces)
 NEXT I NEXT I
Línea 1045: Línea 1050:
  
 <code z80> <code z80>
-    LD A, 1              ; Valor Inicial+    ld a, 1              ; Valor Inicial
 bucle: bucle:
  
     ; Codigo a repetir 20 veces     ; Codigo a repetir 20 veces
  
-    INC A +    inc a 
-    CP 20                ; Valor final +    cp 20                ; Valor final 
-    JR NZ, bucle+    jr nz, bucle
 </code> </code>
  
Línea 1064: Línea 1069:
  
 <code z80> <code z80>
-    LD B, 1+    ld b, 1
 bucle: bucle:
  
     ; Codigo a repetir 20 veces     ; Codigo a repetir 20 veces
  
-    INC B +    inc b 
-    LD AB +    ld ab 
-    CP 20 +    cp 20 
-    JR NZ bucle+    jr NZ bucle
 </code> </code>
  
Línea 1082: Línea 1087:
  
 <code z80> <code z80>
-    LD B, 20+    ld b, 20
 bucle: bucle:
  
     ; Codigo a repetir 20 veces     ; Codigo a repetir 20 veces
  
-    DEC B +    dec b 
-    JR NZ, bucle+    jr nz, bucle
 </code> </code>
  
Línea 1094: Línea 1099:
  
 <code z80> <code z80>
-    LD B, 20+    ld b, 20
 bucle: bucle:
  
     ; Codigo a repetir 20 veces     ; Codigo a repetir 20 veces
  
-    DJNZ, bucle+    djnz, bucle
 </code> </code>
  
  Esta forma de realizar bucles es más rápida en tiempo de ejecución y ocupa menos espacio en el programa que contar desde 1 hasta N.  Esta forma de realizar bucles es más rápida en tiempo de ejecución y ocupa menos espacio en el programa que contar desde 1 hasta N.
  
- Esto permite también implementar bucles de 16 bits de una forma mucho más eficiente, decrementando BC con ''DEC BC'' y comprobando mediante operaciones lógicas si tanto B como C son cero para acabar el bucle:+ Esto permite también implementar bucles de 16 bits de una forma mucho más eficiente, decrementando BC con ''dec bc'' y comprobando mediante operaciones lógicas si tanto B como C son cero para acabar el bucle:
  
 <code z80> <code z80>
-     LD BC, 10000       ; veces a iterar+     ld bc, 10000       ; veces a iterar
  
 bucle: bucle:
Línea 1113: Línea 1118:
     ; ... código de nuestro bucle ...     ; ... código de nuestro bucle ...
  
-    DEC BC              ; Decrementamos el contador +    dec bc              ; Decrementamos el contador 
-    LD A            ; A = B +    ld a            ; A = B 
-    OR C                ; A = A OR C = B OR C +    or c                ; A = A or c = B or c 
-    JP NZ, bucle        ; Si B OR C == 0 => es que BC = 0+    jp nz, bucle        ; Si B or c == 0 => es que BC = 0
 </code> </code>
  
Línea 1126: Línea 1131:
 Hemos visto cómo podemos ejecutar código un número determinado de veces en base a contar desde un determinado valor hasta 0, utilizando los flags para saber cuándo debe de finalizar el bucle y continuar la ejecución del programa. Hemos visto cómo podemos ejecutar código un número determinado de veces en base a contar desde un determinado valor hasta 0, utilizando los flags para saber cuándo debe de finalizar el bucle y continuar la ejecución del programa.
  
-Como veremos más adelante cuando hablemos de posibles optimizaciones, en ciertas ocasiones concretas los desarrolladores evitan utilizar bucles y lo que hacen es **repetir** el mismo bloque de instrucciones N veces, para evitar el salto (''DJNZ'', o ''CP''/''DEC'' + ''JR'' o ''JP'') y su coste en t-estados.+En ciertas ocasiones concretas los desarrolladores evitan utilizar bucles y lo que hacen es **repetir** el mismo bloque de instrucciones N veces, para evitar el salto (''DJNZ'', o ''CP''/''DEC'' + ''JR'' o ''JP'') y su coste en t-estados.
  
 Este proceso se llama **desenrollar el bucle** y consiste en, básicamente, no usar un bucle, sino sustituirlo por N repeticiones del código en el fichero de texto que después ensamblaremos. Para hacer algo así evidentemente necesitamos conocer el número de repeticiones del código. Este proceso se llama **desenrollar el bucle** y consiste en, básicamente, no usar un bucle, sino sustituirlo por N repeticiones del código en el fichero de texto que después ensamblaremos. Para hacer algo así evidentemente necesitamos conocer el número de repeticiones del código.
Línea 1132: Línea 1137:
 El desenrollar el bucle en instrucciones repetidas evita los saltos y por tanto hace que el código tarde menos en ejecutarse que si lo hiciéramos en un bucle, pero a costa de ocupar mucho más espacio, ya que estamos incluyendo los opcodes compilados N veces. El desenrollar el bucle en instrucciones repetidas evita los saltos y por tanto hace que el código tarde menos en ejecutarse que si lo hiciéramos en un bucle, pero a costa de ocupar mucho más espacio, ya que estamos incluyendo los opcodes compilados N veces.
  
-Por ejemplo, supongamos el siguiente código para escribir 8 valores consecutivos $FF en la videomemoria:+Por ejemplo, supongamos el siguiente código para escribir 8 valores consecutivos $ff en la videomemoria:
  
 <code z80> <code z80>
     ORG 50000     ORG 50000
  
-    CALL ROM_CLS+    call ROM_CLS
  
-    LD HL, $4000 +    ld hl, $4000 
-    LD A, $FF +    ld a, $ff 
-    LD B, 8             ; Repetir 8 veces+    ld b, 8             ; Repetir 8 veces
  
 bucle: bucle:
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles +    inc hl              ; Siguiente bloque de pixeles 
-    DJNZ bucle+    djnz bucle
  
-    RET+    ret
  
-ROM_CLS EQU $0DAF+ROM_CLS EQU $0daf
  
     END 50000     END 50000
Línea 1161: Línea 1166:
 \\  \\ 
  
-El programa resultante ocupa 15 bytes hasta el ''RET'' (opcode $C9):+El programa resultante ocupa 15 bytes hasta el ''RET'' (opcode $c9):
  
 +\\ 
 <code> <code>
 $ hexdump -C bucle.bin $ hexdump -C bucle.bin
 00000000  cd af 0d 21 00 40 3e ff  06 08 77 23 10 fc c9     |...!.@>...w#...| 00000000  cd af 0d 21 00 40 3e ff  06 08 77 23 10 fc c9     |...!.@>...w#...|
 </code> </code>
 +\\ 
  
 Pero dado que el bucle tiene un número de iteraciones fijo, también podríamos haber desenrollado el bucle y haber escrito lo siguiente: Pero dado que el bucle tiene un número de iteraciones fijo, también podríamos haber desenrollado el bucle y haber escrito lo siguiente:
Línea 1173: Línea 1180:
     ORG 50000     ORG 50000
  
-    CALL ROM_CLS+    call ROM_CLS
  
-    LD HL, $4000 +    ld hl, $4000 
-    LD A, $FF+    ld a, $ff
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
  
-    LD (HL),          ; Escribir valor en pantalla+    ld (hl),          ; Escribir valor en pantalla
  
-    ; El ultimo INC HL no es necesario ya que no vamos+    ; El ultimo inc hl no es necesario ya que no vamos
     ; a escribir en el siguiente bloque de pixeles.     ; a escribir en el siguiente bloque de pixeles.
  
-    RET+    ret
  
-ROM_CLS EQU $0DAF+ROM_CLS EQU $0daf
  
     END 50000     END 50000
Línea 1214: Línea 1221:
  
    * Ya no necesitamos usar el registro B para el bucle, así que tenemos un registro más disponible para trabajar.    * Ya no necesitamos usar el registro B para el bucle, así que tenemos un registro más disponible para trabajar.
 +
    * La instrucción para cargar B con un valor ya no es necesaria (-2 bytes y 7 ciclos de reloj menos de ejecución del programa).    * La instrucción para cargar B con un valor ya no es necesaria (-2 bytes y 7 ciclos de reloj menos de ejecución del programa).
-   * El ''DJNZ'' tampoco es necesario ya (-2 bytes y 13 ciclos de reloj menos en cada iteración del bucle y 7 menos al acabar el bucle, es decir, ¡98 t-estados menos!). + 
-   * El último ''INC HL'' ya no es necesario, mientras que en el caso del bucle se habría ejecutado (-1 byte y 6 ciclos de reloj menos).+   * El ''djnz'' tampoco es necesario ya (-2 bytes y 13 ciclos de reloj menos en cada iteración del bucle y 7 menos al acabar el bucle, es decir, ¡98 t-estados menos!). 
 + 
 +   * El último ''inc hl'' ya no es necesario, mientras que en el caso del bucle se habría ejecutado (-1 byte y 6 ciclos de reloj menos).
  
 En total nuestro código es ahora 111 ciclos de reloj más rápido. Esto nos puede parecer de poca importancia en un ejemplo como este, pero si lo hacemos dentro de una rutina para dibujar los gráficos del juego, o para hacer cálculos importantes en el bucle principal del programa, puede suponer una gran diferencia. En total nuestro código es ahora 111 ciclos de reloj más rápido. Esto nos puede parecer de poca importancia en un ejemplo como este, pero si lo hacemos dentro de una rutina para dibujar los gráficos del juego, o para hacer cálculos importantes en el bucle principal del programa, puede suponer una gran diferencia.
  
-Pero el haber desenrollado el bucle tiene un coste. Nuestro programa de 15 bytes ocupa ahora 24 bytes, ya que hemos tenido que repetir código 8 veces:+Sin embargo, el haber desenrollado el bucle tiene una desventaja, un coste:
  
-<code z80>+  *  Nuestro programa de 15 bytes ocupa ahora 24 bytes, ya que hemos tenido que repetir código 8 veces: 
 + 
 +\\  
 +<code>
 $ hexdump -C bucle_desenrollado.bin $ hexdump -C bucle_desenrollado.bin
 00000000  cd af 0d 21 00 40 3e ff  77 23 77 23 77 23 77 23  |...!.@>.w#w#w#w#| 00000000  cd af 0d 21 00 40 3e ff  77 23 77 23 77 23 77 23  |...!.@>.w#w#w#w#|
 00000010  77 23 77 23 77 23 77 c9                           |w#w#w#w.| 00000010  77 23 77 23 77 23 77 c9                           |w#w#w#w.|
 </code> </code>
 +\\ 
  
-Como puede apreciarse, ahora tenemos repetidas varias veces las instrucciones ''LD (HL), A'' ($77) y ''INC HL'' ($23) en el binario resultante.+Como puede apreciarse, ahora tenemos repetidas varias veces las instrucciones ''ld (hl), a'' ($77) y ''inc hl'' ($23) en el binario resultante.
  
 En este caso sólo pasamos de 15 a 24 bytes, pero si dentro del bucle tuviéramos no 2 instrucciones sino 20 ó 30, podríamos estar consumiendo muchos cientos de bytes sólo para desenrollar el bucle, y recordemos que la memoria es limitada, con unos 25-35KB libres en un Spectrum 48K para la totalidad del programa. En este caso sólo pasamos de 15 a 24 bytes, pero si dentro del bucle tuviéramos no 2 instrucciones sino 20 ó 30, podríamos estar consumiendo muchos cientos de bytes sólo para desenrollar el bucle, y recordemos que la memoria es limitada, con unos 25-35KB libres en un Spectrum 48K para la totalidad del programa.
  
-Así, lo normal es "desenrollar" sólo aquellos bucles que sean críticos en el programa. No tiene sentido desenrollar un bucle en un sitio donde la velocidad no es crucial, a costa de que el programa ocupe más, tarde más en cargar, y tengamos el riesgo de quedarnos sin espacio para desarrollar el resto del programa.+Así, **lo normal es "desenrollar" sólo aquellos bucles que sean críticos en velocidad para el programa**. No tiene sentido desenrollar un bucle en un sitio donde la velocidad no es crucial, a costa de que el programa ocupe más, tarde más en cargar, y tengamos el riesgo de quedarnos sin espacio para desarrollar el resto del programa.
  
 Cabe destacar que los programas ensambladores suelen disponer de directivas para **repetir código** sin tener que escribirlo varias veces. Cabe destacar que los programas ensambladores suelen disponer de directivas para **repetir código** sin tener que escribirlo varias veces.
Línea 1241: Línea 1255:
  
 <code z80> <code z80>
-    ; En pasmo +    ; En pasmo: REPT - ENDM 
-    LD A, $FF+    ld a, $ff
  
     REPT 8     REPT 8
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
     ENDM     ENDM
  
-    LD (HL),          ; Escribir valor en pantalla+    ld (hl),          ; Escribir valor en pantalla
 </code> </code>
  
 <code z80> <code z80>
-    ; En sjasmplus +    ; En sjasmplus: REPT - ENDR 
-    LD A, $FF+    ld a, $ff
  
     REPT 8     REPT 8
-    LD (HL),          ; Escribir valor en pantalla +    ld (hl),          ; Escribir valor en pantalla 
-    INC HL              ; Siguiente bloque de pixeles+    inc hl              ; Siguiente bloque de pixeles
     ENDR     ENDR
  
-    LD (HL),          ; Escribir valor en pantalla+    ld (hl),          ; Escribir valor en pantalla
 </code> </code>
  
Línea 1270: Línea 1284:
 ===== Instrucciones de comparacion repetitivas ===== ===== Instrucciones de comparacion repetitivas =====
  
- Para acabar con las instrucciones de comparación vamos a ver las instrucciones de comparación repetitivas. Son parecidas a ''CP'', pero trabajan (igual que LDILDIRLDD LDDR) con HL y BC para realizar las comparaciones con la memoria: son ''CPI'', ''CPD'', ''CPIR'' y ''CPDR''.+ Para acabar con las instrucciones de comparación vamos a ver las instrucciones de comparación repetitivas. Son parecidas a ''CP'', pero trabajan (igual que ldildirldd lddr) con HL y BC para realizar las comparaciones con la memoria: son ''CPI'', ''CPD'', ''CPIR'' y ''CPDR''.
  
- Comencemos con **CPI (ComPare and Increment)**:+ Comencemos con **cpi (ComPare and Increment)**:
  
 \\  \\ 
 \\  \\ 
-**CPI:**+**cpi:**
 \\  \\ 
   * Al registro A se le resta el byte contenido en la posición de memoria apuntada por HL.   * Al registro A se le resta el byte contenido en la posición de memoria apuntada por HL.
Línea 1286: Línea 1300:
   * Se decrementa BC.   * Se decrementa BC.
  
- Técnicamente (con un pequeño matiz que veremos ahora), CPI equivale a:+ Técnicamente (con un pequeño matiz que veremos ahora), cpi equivale a:
  
 <code> <code>
-CPI =     CP [HL] +cpi =     cp (hl) 
-          INC HL +          inc hl 
-          DEC BC+          dec bc
 </code> </code>
  
 \\  \\ 
-**CPD:**+**cpd:**
 \\  \\ 
- Su instrucción "hermana" **CPD (ComPare and Decrement)** funciona de idéntica forma, pero decrementando HL:+ Su instrucción "hermana" **cpd (ComPare and Decrement)** funciona de idéntica forma, pero decrementando HL:
  
 <code> <code>
-CPD =     CP [HL] +cpd =     cp (hl) 
-          DEC HL +          dec hl 
-          DEC BC+          dec bc
 </code> </code>
  
- Y el pequeño matiz: así como CP [HL] afecta al indicador C de Carry, //CPI CPD//, aunque realizan esa operación intermedia, //no lo afectan//.+ Y el pequeño matiz: así como ''cp (hl)'' afecta al indicador C de Carry, //cpi cpd//, aunque realizan esa operación intermedia, //no lo afectan//.
  
  Las instrucciones ''CPIR'' y ''CPDR'' son equivalentes a ''CPI'' y ''CPD'', pero ejecutándose múltiples veces: hasta que BC sea cero o bien se encuentre en la posición de memoria apuntada por HL un valor numérico igual al que contiene el registro A. Literalmente, //es una instrucción de búsqueda//: buscamos hacia adelante (''CPIR'') o hacia atrás (''CPDR''), desde una posición de memoria inicial (HL), un valor (A), entre dicha posición inicial (HL) y una posición final (HL+BC o HL-BC para ''CPIR'' y ''CPDR'').  Las instrucciones ''CPIR'' y ''CPDR'' son equivalentes a ''CPI'' y ''CPD'', pero ejecutándose múltiples veces: hasta que BC sea cero o bien se encuentre en la posición de memoria apuntada por HL un valor numérico igual al que contiene el registro A. Literalmente, //es una instrucción de búsqueda//: buscamos hacia adelante (''CPIR'') o hacia atrás (''CPDR''), desde una posición de memoria inicial (HL), un valor (A), entre dicha posición inicial (HL) y una posición final (HL+BC o HL-BC para ''CPIR'' y ''CPDR'').
Línea 1311: Línea 1325:
 \\  \\ 
 \\  \\ 
-**CPIR:**+**cpir:**
 \\  \\ 
   * Al registro A se le resta el byte contenido en la posición de memoria apuntada por HL.   * Al registro A se le resta el byte contenido en la posición de memoria apuntada por HL.
Línea 1324: Línea 1338:
 \\  \\ 
 \\  \\ 
-**CPDR:**+**cpdr:**
 \\  \\ 
  ''CPDR'' es, como podéis imaginar, el equivalente a ''CPIR'' pero decrementando HL, para buscar hacia atrás en la memoria.  ''CPDR'' es, como podéis imaginar, el equivalente a ''CPIR'' pero decrementando HL, para buscar hacia atrás en la memoria.
Línea 1334: Línea 1348:
    Instrucción       |S Z H P N C|    Instrucción       |S Z H P N C|
  ----------------------------------  ----------------------------------
- |CPI                |* * * * 1 -| + |cpi                |* * * * 1 -| 
- |CPD                |* * * * 1 -| + |cpd                |* * * * 1 -| 
- |CPIR               |* * * * 1 -| + |cpir               |* * * * 1 -| 
- |CPDR               |* * * * 1 -|+ |cpdr               |* * * * 1 -|
 </code> </code>
  
Línea 1345: Línea 1359:
  
 <code z80> <code z80>
-    LD HL, 20000      ; Origen de la busqueda +    ld hl, 20000      ; Origen de la busqueda 
-    LD BC, 10000      ; Número de bytes a buscar (20000-30000) +    ld bc, 10000      ; Número de bytes a buscar (20000-30000) 
-    LD A, 123         ; Valor a buscar +    ld a, 123         ; Valor a buscar 
-    CPIR+    cpir
 </code> </code>
  
Línea 1358: Línea 1372:
   = 123   = 123
  
-CPIR =+cpir =
 Repetir: Repetir:
   Leer el contenido de (HL)   Leer el contenido de (HL)
-  Si A==(HL) -> Fin_de_CPIR +  Si A==(HL) -> Fin_de_cpir 
-  Si BC==0   -> Fin_de_CPIR+  Si BC==0   -> Fin_de_cpir
   HL = HL+1   HL = HL+1
   BC = BC-1   BC = BC-1
-Fin_de_CPIR:+Fin_de_cpir:
 </code> </code>
  
  Con esto, si la celdilla 15000 contiene el valor "123", la instrucción ''CPIR'' del ejemplo anterior acabará su ejecución, dejando en HL el valor 15001 (tendremos que decrementar HL para obtener la posición exacta). Dejará además el flag "P/O" (paridad/desbordamiento) y el flag Z a uno. En BC tendremos restado el número de iteraciones del "bucle" realizadas.  Con esto, si la celdilla 15000 contiene el valor "123", la instrucción ''CPIR'' del ejemplo anterior acabará su ejecución, dejando en HL el valor 15001 (tendremos que decrementar HL para obtener la posición exacta). Dejará además el flag "P/O" (paridad/desbordamiento) y el flag Z a uno. En BC tendremos restado el número de iteraciones del "bucle" realizadas.
  
- Si no se encuentra ninguna aparición de "123", BC llegará a valer cero, porque el "bucle CPI" se ejecutará 10000 veces. El flag P/O estará a cero, al igual que Z, indicando que se finalizó el ''CPIR'' y no se encontró nada.+ Si no se encuentra ninguna aparición de "123", BC llegará a valer cero, porque el "bucle cpi" se ejecutará 10000 veces. El flag P/O estará a cero, al igual que Z, indicando que se finalizó el ''CPIR'' y no se encontró nada.
  
- Nótese que si en vez de utilizar ''CPIR'' hubiéramos utilizado ''CPDR'', podríamos haber buscado hacia atrás, desde 20000 a 10000, decrementando HL. Incluso haciendo HL=0 y usando ''CPDR'', podemos encontrar la última aparición del valor de A en la memoria (ya que 0000 - 1 = $FFFF, es decir: 0-1=65535 en nuestros 16 bits).+ Nótese que si en vez de utilizar ''CPIR'' hubiéramos utilizado ''CPDR'', podríamos haber buscado hacia atrás, desde 20000 a 10000, decrementando HL. Incluso haciendo HL=0 y usando ''CPDR'', podemos encontrar la última aparición del valor de A en la memoria (ya que 0000 - 1 = $ffff, es decir: 0-1=65535 en nuestros 16 bits).
  
 \\  \\ 
Línea 1383: Línea 1397:
     ORG 50000     ORG 50000
  
-    LD HL, texto     ; Inicio de la busqueda +    ld hl, texto        ; Inicio de la busqueda 
-    LD A, 'X'        ; Carácter (byte) a buscar +    ld a, 'X'           ; Carácter (byte) a buscar 
-    LD BC, 100       ; Número de bytes donde buscar +    ld bc, 100          ; Número de bytes donde buscar 
-    CPIR             ; Realizamos la búsqueda+    cpir                ; Realizamos la búsqueda
  
-    JP NZ, No_Hay    ; Si no encontramos el caracter buscado +    jp nz, No_Hay       ; Si no encontramos el caracter buscado 
-                     ; el flag de Z estará a cero.+                        ; el flag de Z estará a cero.
  
-                     ; Si seguimos por aquí es que se encontró +                        ; Si seguimos por aquí es que se encontró 
-    DEC HL           ; Decrementamos HL para apuntar al byte +    dec hl              ; Decrementamos HL para apuntar al byte 
-                     ; encontrado en memoria.+                        ; encontrado en memoria.
  
-    LD BC, texto +    ld bc, texto 
-    SCF +    scf 
-    CCF              ; Ponemos el carry flag a 0 (SCF+CCF+    ccf                 ; Ponemos el carry flag a 0 (scf+ccf
-    SBC HLBC       ; HL = HL - BC +    sbc hlbc          ; HL = HL - BC 
-                     ;    = (posicion encontrada) - (inicio cadena) +                        ;    = (posicion encontrada) - (inicio cadena) 
-                     ;    = posición de 'X' dentro de la cadena.+                        ;    = posición de 'X' dentro de la cadena.
  
-    LD BH +    ld bh 
-    LD CL          ; BC = HL+    ld cl             ; BC = HL
  
-    RET              ; Volvemos a basic con el resultado en BC+    ret                 ; Volvemos a basic con el resultado en BC
  
 No_Hay: No_Hay:
-    LD BC, $FFFF +    ld bc, $ffff 
-    RET+    ret
  
 texto DB "Esto es una X cadena de texto." texto DB "Esto es una X cadena de texto."
Línea 1429: Línea 1443:
    * Hacemos HL = posición de memoria donde empieza la cadena.    * Hacemos HL = posición de memoria donde empieza la cadena.
    * Hacemos A = 'X'.    * Hacemos A = 'X'.
-   * Ejecutamos un ''CPIR''+   * Ejecutamos un ''cpir''
    * En HL obtendremos la posición absoluta + 1 donde se encuentra el carácter 'X' encontrado (o FFFFh si no se encuentra). Exactamente 50041.    * En HL obtendremos la posición absoluta + 1 donde se encuentra el carácter 'X' encontrado (o FFFFh si no se encuentra). Exactamente 50041.
    * Decrementamos HL para que apunte a la 'X' (50040).    * Decrementamos HL para que apunte a la 'X' (50040).
Línea 1435: Línea 1449:
    * Volvemos al BASIC con el resultado en BC. El ''PRINT USR 50000'' imprimirá dicho valor de retorno.    * Volvemos al BASIC con el resultado en BC. El ''PRINT USR 50000'' imprimirá dicho valor de retorno.
  
- Nótese que el bloque desde ''SCF'' hasta ''LD CL'' tiene como objetivo ser el equivalente a ''HL = HL - BC'', y se tiene que hacer de esta forma porque no existe ''SUB HLBC'' ni ''LD BCHL'':+ Nótese que el bloque desde ''scf'' hasta ''ld cl'' tiene como objetivo ser el equivalente a ''HL = HL - BC'', y se tiene que hacer de esta forma porque no existe ''sub hlbc'' ni ''ld bchl'':
  
 <code> <code>
-SUB HLBC =  SCF +sub hlbc =  scf 
-              CCF              ; Ponemos el carry flag a 0 (SCF+CCF+              ccf              ; Ponemos el carry flag a 0 (scf+ccf
-              SBC HLBC       ; HL = HL - BC+              sbc hlbc       ; HL = HL - BC
  
-LD BCHL  =  LD BH +ld bchl  =  ld bh 
-              LD C         ; BC = HL+              ld c         ; BC = HL
 </code> </code>
  
Línea 1460: Línea 1474:
   * {{cursos:ensamblador:05_db.asm|La directiva DB utilizada en un ejemplo.}}   * {{cursos:ensamblador:05_db.asm|La directiva DB utilizada en un ejemplo.}}
   * {{cursos:ensamblador:05_db.tap|Fichero tap del ejemplo db.asm.}}   * {{cursos:ensamblador:05_db.tap|Fichero tap del ejemplo db.asm.}}
-  * {{cursos:ensamblador:05_bucle.asm|Ejemplo de bucle infinito con JP.}}+  * {{cursos:ensamblador:05_bucle.asm|Ejemplo de bucle infinito con jp.}}
   * {{cursos:ensamblador:05_bucle.tap|Fichero tap del ejemplo bucle.asm.}}   * {{cursos:ensamblador:05_bucle.tap|Fichero tap del ejemplo bucle.asm.}}
-  * {{cursos:ensamblador:05_buscatxt.asm|Búsqueda de cadenas de texto con CPIR.}}+  * {{cursos:ensamblador:05_buscatxt.asm|Búsqueda de cadenas de texto con cpir.}}
   * {{cursos:ensamblador:05_buscatxt.tap|Fichero tap del ejemplo buscatxt.asm.}}   * {{cursos:ensamblador:05_buscatxt.tap|Fichero tap del ejemplo buscatxt.asm.}}
  
  • cursos/ensamblador/lenguaje_3.1705249134.txt.gz
  • Última modificación: 14-01-2024 16:18
  • por sromero