Diferencias
Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
cursos:ensamblador:lenguaje_3 [07-01-2024 09:04] – [Definir variables y datos y referenciarlos con etiquetas] sromero | cursos:ensamblador:lenguaje_3 [19-01-2024 07:14] (actual) – sromero | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
+ | |||
====== Lenguaje Ensamblador del Z80 (III) ====== | ====== Lenguaje Ensamblador del Z80 (III) ====== | ||
- | ====== Instrucciones condicionales | + | ===== Instrucciones condicionales, saltos y bucles |
- | Una vez hemos visto la mayoría de instrucciones aritméticas y lógicas, es el | + | Una vez hemos visto la mayoría de instrucciones aritméticas y lógicas, es el momento de utilizarlas como condicionales para realizar cambios en el flujo lineal de nuestro programa. En esta entrega aprenderemos a usar etiquetas y saltos mediante instrucciones condicionales ('' |
- | momento de utilizarlas como condicionales para realizar cambios en el flujo | + | |
- | lineal de nuestro programa. En esta entrega aprenderemos a usar etiquetas y | + | |
- | saltos mediante instrucciones condicionales (**CP**, **JR + condición**, **JP + condición**, | + | |
- | etc.), lo que nos permitirá implementar en ensamblador las típicas instrucciones | + | |
- | IF/ | + | |
Línea 14: | Línea 10: | ||
===== Las etiquetas en los programas ASM ===== | ===== Las etiquetas en los programas ASM ===== | ||
- | Las **etiquetas** son unas útiles directivas de los ensambladores que nos | + | Las **etiquetas** son unas útiles directivas de los ensambladores que nos permitirán //hacer referencia a posiciones concretas de memoria// por medio de nombres, en lugar de tener que utilizar valores numéricos. |
- | permitirán //hacer referencia a posiciones concretas de memoria// por medio de nombres, | + | |
- | en lugar de tener que utilizar valores numéricos. | + | |
Las etiquetas aparecen al principio de una línea de assembler y pueden tener (o no) tener dos puntos para finalizarlas. Los dos puntos son opcionales, y hay programadores que prefieren ponerlos, y otros que no. Como veremos en muchos ejemplos, es habitual ponerlas en las etiquetas de salto y no ponerlas en las referencias a variables salvo que ocupen varias líneas. | Las etiquetas aparecen al principio de una línea de assembler y pueden tener (o no) tener dos puntos para finalizarlas. Los dos puntos son opcionales, y hay programadores que prefieren ponerlos, y otros que no. Como veremos en muchos ejemplos, es habitual ponerlas en las etiquetas de salto y no ponerlas en las referencias a variables salvo que ocupen varias líneas. | ||
Línea 23: | Línea 17: | ||
<code z80> | <code z80> | ||
- | | + | |
+ | |||
+ | nop | ||
+ | ld b, 10 | ||
- | NOP | ||
- | LD B, 10 | ||
bucle: | bucle: | ||
- | LD A, 20 | + | ld a, 20 |
- | NOP | + | nop |
- | (...) | + | (...) |
- | | + | |
- | RET | + | ret |
</ | </ | ||
Línea 38: | Línea 33: | ||
< | < | ||
- | 00 06 0a 3e 14 00 (...) c3 53 c3 c9 | + | 00 06 0a 3e 14 00 (...) c3 53 c3 c9 |
</ | </ | ||
Línea 44: | Línea 39: | ||
|< 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 57: | Línea 52: | ||
< | < | ||
- | 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 |
</ | </ | ||
- | | + | |
ensamblador, | ensamblador, | ||
- | 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 |
- | 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á " | + | 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 |
- | | + | |
posición 50003, pero recordemos que esto no es una instrucción, | posición 50003, pero recordemos que esto no es una instrucción, | ||
- | sólo para el programa ensamblador. Por eso, cuando el ensamblador encuentra la etiqueta | + | sólo para el programa ensamblador. Por eso, cuando el ensamblador encuentra la etiqueta |
texto " | texto " | ||
Si la etiqueta fuera una instrucción, | Si la etiqueta fuera una instrucción, | ||
- | agrega a una tabla interna de referencias, | + | agrega a una tabla interna de referencias, |
\\ | \\ | ||
Línea 86: | Línea 81: | ||
\\ | \\ | ||
- | Lo que realmente ensamblará en la dirección 50003 (y en la 50004) es la instrucción siguiente: | + | Lo que realmente ensamblará en la dirección 50003 (y en la 50004) es la instrucción siguiente: |
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 " | 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 " | ||
El siguiente programa es equivalente al anterior, pero sin usar etiquetas: | El siguiente programa es equivalente al anterior, pero sin usar etiquetas: | ||
- | + | ||
<code z80> | <code z80> | ||
- | ORG 50000 | + | |
+ | |||
+ | nop | ||
+ | ld b, 10 | ||
+ | ld a, 20 | ||
+ | nop | ||
+ | (...) | ||
+ | jp 50003 | ||
+ | ret | ||
- | NOP | + | END |
- | LD B, 10 | + | |
- | LD A, 20 | + | |
- | NOP | + | |
- | | + | |
- | JP 50003 | + | |
- | RET | + | |
</ | </ | ||
- | En este caso, " | + | En este caso, '' |
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), | + | Imaginemos que una vez acabado nuestro programa sin etiquetas (utilizando sólo direcciones numéricas), |
- | 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 '' |
- | añadiéramos un NOP extra al principio del mismo, ya no habría que saltar a 50003 sino a 50004: | + | |
<code z80> | <code z80> | ||
- | ORG 50000 | + | |
- | NOP | + | nop |
- | 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 | + | |
- | RET | + | ret |
+ | |||
+ | END | ||
</ | </ | ||
- | 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 " | + | 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 '' |
- | Como veremos posteriormente, | + | Como veremos posteriormente, |
- | JP XX hace el registro PC = XX, 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> | ||
- | ORG 50000 | + | |
; Al salir de esta rutina, A=tecla pulsada | ; Al salir de esta rutina, A=tecla pulsada | ||
RutinaLeerTeclado: | RutinaLeerTeclado: | ||
| | ||
- | 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 | ||
RutinaDibujarSprite: | RutinaDibujarSprite: | ||
(...) | (...) | ||
- | bucle1: | + | bucle1: |
- | | + | (instrucciones) |
- | | + | bucle2: |
- | | + | (instrucciones) |
- | | + | pintar: |
- | | + | (instrucciones) |
- | JP bucle1 | + | |
- | | + | (...) |
| | ||
- | RET | + | ret |
- | + | ||
- | | + | (etc...) |
+ | END | ||
</ | </ | ||
- | + | ||
Así, podremos especificar múltiples etiquetas para hacer referencia a todas las posiciones que necesitemos dentro de nuestro programa. | Así, podremos especificar múltiples etiquetas para hacer referencia a todas las posiciones que necesitemos dentro de nuestro programa. | ||
Línea 166: | Línea 165: | ||
etiqueta: | etiqueta: | ||
;;; (más código) | ;;; (más código) | ||
- | | + | |
</ | </ | ||
Línea 172: | Línea 171: | ||
<code z80> | <code z80> | ||
- | | + | |
;;; (más código) | ;;; (más código) | ||
etiqueta: | etiqueta: | ||
Línea 183: | Línea 182: | ||
\\ | \\ | ||
- | | + | |
<code z80> | <code z80> | ||
- | DB 0 | + | |
- | | + | DB "Esto es una cadena" |
- | | + | DB " |
- | | + | DW 12345 |
</ | </ | ||
Línea 197: | Línea 196: | ||
Como ya hemos visto, la aparición de una etiqueta en el código marca para el programa ensamblador "la posición del programa en este punto", | Como ya hemos visto, la aparición de una etiqueta en el código marca para el programa ensamblador "la posición del programa en este punto", | ||
- | Como simplemente marcan una determinada posición de memoria, no importa si lo que viene después de una etiqueta es código, datos, o múltiples datos. La etiqueta simplemente referencia ese punto, esa dirección de ensamblado, y podemos | + | Como simplemente marcan una determinada posición de memoria, no importa si lo que viene después de una etiqueta es código, datos, o múltiples datos. La etiqueta simplemente referencia ese punto, esa dirección de ensamblado, y podemos |
<code z80> | <code z80> | ||
MiRutina: | MiRutina: | ||
- | | + | |
- | + | ||
bucle: | bucle: | ||
- | | + | |
- | RET | + | ret |
- | | + | |
variable1 | variable1 | ||
variable2: | variable2: | ||
- | DB 0, 0, 0, 0, 0, 0, 0 | + | |
- | | + | |
variable3: | variable3: | ||
- | DB 0, 1, 1, 1, 1, 1, 0 | + | |
- | | + | DB 1, 0, 0, 0, 0, 0, 1 |
</ | </ | ||
Línea 223: | Línea 222: | ||
<code z80> | <code z80> | ||
; Demostracion de datos y variables en memoria | ; Demostracion de datos y variables en memoria | ||
- | ORG 50000 | + | |
; Primero vamos a copiar unos datos a la videomemoria. | ; Primero vamos a copiar unos datos a la videomemoria. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; 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 " | ; un total de " | ||
- | | + | |
- | | + | |
- | | + | |
bucle: | bucle: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | 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 | longitud_datos | ||
Línea 275: | Línea 274: | ||
texto DB "Esto es una cadena de texto", | texto DB "Esto es una cadena de texto", | ||
- | ;; Incluimos nuestra libreria aqui | + | |
- | INCLUDE " | + | INCLUDE " |
- | END 50000 | + | |
</ | </ | ||
Línea 289: | Línea 288: | ||
También hemos insertado una etiqueta llamada " | También hemos insertado una etiqueta llamada " | ||
- | 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 | + | 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 |
- | 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 " | + | 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 ('' |
- | 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: |
< | < | ||
$ pasmo --bin db.asm db.bin | $ pasmo --bin db.asm db.bin | ||
- | $hexdump -C 05_db.bin | + | $ hexdump -C 05_db.bin |
00000000 | 00000000 | ||
00000010 | 00000010 | ||
Línea 321: | Línea 320: | ||
</ | </ | ||
- | El contenido del binario que estamos viendo arriba es lo que el cargador BASIC " | + | El contenido del binario que estamos viendo arriba es lo que el cargador BASIC " |
- | El primer opcode es " | + | El primer opcode es " |
- | A continuación podemos ver el opcode $11 $00 $40 que se corresponde con "**LD DE, 16384** (16384 es $4000). | + | Los 2 siguientes bytes son pues la dirección de la etiqueta " |
+ | |||
+ | A continuación podemos ver el opcode | ||
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 331: | 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, | 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, | ||
- | Pero sigamos viendo el resultado del ensamblado: podemos ver en el " | + | Pero sigamos viendo el resultado del ensamblado: podemos ver en el " |
- | 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 '' |
<code z80> | <code z80> | ||
Línea 339: | Línea 340: | ||
</ | </ | ||
- | Todo el código desde después de dicho $FF hasta el final del binario es el código ensamblado de nuestra librería | + | Todo el código desde después de dicho $ff hasta el final del binario es el código ensamblado de nuestra librería |
- | Volvamos de nuevo a las etiquetas y a DB: Cuando en el programa hacemos | + | Volvamos de nuevo a las etiquetas y a DB: Cuando en el programa hacemos |
- | Lo mismo ocurre con el texto que se ha definido entre dobles comillas. A partir de la dirección definida por "texto" | + | Lo mismo ocurre con el texto que se ha definido entre dobles comillas. A partir de la dirección definida por '' |
- | | + | |
\\ | \\ | ||
Línea 354: | Línea 355: | ||
<code z80> | <code z80> | ||
- | numeros_primos | + | numeros_primos |
</ | </ | ||
Línea 360: | Línea 361: | ||
<code z80> | <code z80> | ||
- | vidas DB 3 | + | |
- | | + | x DB 0 |
- | | + | y DB 0 |
- | | + | ancho DB 16 |
- | | + | alto |
- | | + | |
+ | |||
+ | ld a, (vidas) | ||
+ | | ||
- | LD A, (vidas) | ||
- | (...) | ||
muerte: | muerte: | ||
- | DEC A | + | dec a |
- | LD (vidas), | + | |
</ | </ | ||
Línea 377: | Línea 379: | ||
<code z80> | <code z80> | ||
- | Enemigo: | + | Enemigo: |
DB 12, 13, 25, 123, 210 (etc...) | DB 12, 13, 25, 123, 210 (etc...) | ||
</ | </ | ||
- | Ahora bien, es muy importante tener clara una consideración: | + | Ahora bien, es muy importante tener clara una consideración: |
<code z80> | <code z80> | ||
- | ORG 50000 | + | |
- | ; Cuidado, al situar los datos aquí, cuando saltemos a 50000 | + | |
- | ; con RANDOMIZE USR 50000, ejecutaremos estos datos como si | + | ; RANDOMIZE USR, ejecutaremos estos datos como si fueran opcodes. |
- | ; fueran opcodes. | + | |
- | datos DB 00, 201, 100, 12, 255, 11 | + | |
+ | |||
+ | ld b, a | ||
+ | (más instrucciones) | ||
+ | ret | ||
- | LD B, A | + | |
- | (más instrucciones) | + | |
- | RET | + | |
- | END 50000 | + | |
</ | </ | ||
Línea 400: | Línea 403: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | ; datos que habíamos introducido antes. | + | ; datos que habíamos introducido antes. |
- | LD B, A | + | ld b, a |
- | (más instrucciones) | + | |
- | RET | + | |
- | ; Aquí nunca serán ejecutados, el RET está antes. | + | (más instrucciones) |
+ | |||
+ | ret | ||
+ | |||
+ | ; 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 | ||
- | END 50000 | + | |
</ | </ | ||
- | + | ||
//Los microprocesadores como el Z80 no saben distinguir entre datos e instrucciones//, | //Los microprocesadores como el Z80 no saben distinguir entre datos e instrucciones//, | ||
- | ejecutar datos como si fueran códigos de instrucción del Z80. De hecho, si hacemos un RANDOMIZE USR XX (siendo XX cualquier valor de | + | ejecutar datos como si fueran códigos de instrucción del Z80. De hecho, si hacemos un '' |
la memoria fuera de la ROM), lo más probable es que ejecutemos datos como si fueran instrucciones y el Spectrum se cuelgue, ya que los datos no son parte de un programa, y la ejecución resultante de interpretar esos datos no tendría ningún sentido. | la memoria fuera de la ROM), lo más probable es que ejecutemos datos como si fueran instrucciones y el Spectrum se cuelgue, ya que los datos no son parte de un programa, y la ejecución resultante de interpretar esos datos no tendría ningún sentido. | ||
Línea 421: | Línea 426: | ||
===== Saltos absolutos incondicionales: | ===== Saltos absolutos incondicionales: | ||
- | Ya sabemos definir etiquetas en nuestros programas y referenciarlas. Ahora la pregunta | + | Ya sabemos definir etiquetas en nuestros programas y referenciarlas. Ahora la pregunta es: ¿para qué sirven estas etiquetas? Aparte de referencias para usarlas como variables o datos, su principal uso será saltar a ellas con las // |
- | es: ¿para qué sirven estas etiquetas? Aparte de referencias para | + | |
- | usarlas como variables o datos, su principal uso será saltar a ellas con las | + | |
- | // | + | |
- | Para empezar vamos a ver 2 instrucciones de salto incondicionales, | + | Para empezar vamos a ver 2 instrucciones de salto incondicionales, |
- | lleguemos a una de esas 2 instrucciones, | + | |
- | la ejecución del programa. De esta forma podremos realizar bucles, | + | |
- | saltos a rutinas o funciones, etc. | + | |
- | | + | |
<code z80> | <code z80> | ||
- | | + | |
- | ORG 50000 | + | ORG 50000 |
- | XOR A ; A = 0 | + | xor a ; A = 0 |
bucle: | bucle: | ||
- | INC A ; A = A + 1 | + | inc a ; A = A + 1 |
- | | + | |
- | | + | |
- | RET ; Esto nunca se ejecutará | + | ret ; Esto nunca se ejecutará |
- | + | ||
- | END 50000 | + | END 50000 |
</ | </ | ||
- | | + | |
- | 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. | + | |
- | Tras esta escritura, encontramos | + | Nada más entrar en 50000, se ejecuta |
- | 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 " | + | |
- | salto incondicional | + | |
- | repitiendo una y otra vez la misma porción de código, | + | |
- | los 8 primeros | + | |
- | a 255 continuadamente). | + | |
- | // | + | Tras esta escritura, encontramos un '' |
- | porciones | + | |
- | lo que se conoce como "SALTO INCONDICIONAL ABSOLUTO" | + | |
- | posición absoluta de memoria | + | |
- | de dicho valor al registro PC. | + | |
- | | + | // |
+ | |||
+ | Existen 3 maneras de usar '' | ||
\\ | \\ | ||
- | a.- **JP NN**: | + | a.- **jp NN**: |
- | Saltar a la dirección NN. | + | Saltar a la dirección NN. Literalmente: |
- | Literalmente: | + | |
\\ | \\ | ||
\\ | \\ | ||
- | b.- **JP (HL)** | + | b.- **jp (hl)** |
- | Saltar a la dirección contenida en el registro HL (ojo, no | + | 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: |
- | a la dirección apuntada por el registro HL, sino directamente | + | |
- | a su valor). | + | |
- | Literalmente: | + | |
\\ | \\ | ||
\\ | \\ | ||
- | c.- **JP (registro_indice)** | + | c.- **jp (registro_indice)** |
- | Saltar a la dirección contenida en IX o IY. | + | Saltar a la dirección contenida en IX o IY. Literalmente: |
- | Literalmente: | + | |
\\ | \\ | ||
Línea 499: | Línea 478: | ||
< | < | ||
- | | + | Flags |
| | ||
| | ||
- | JP NN |- - - - - -| | + | jp NN |- - - - - -| |
- | JP (HL) |- - - - - -| | + | jp (hl) |- - - - - -| |
- | JP (IX) |- - - - - -| | + | jp (ix) |- - - - - -| |
- | JP (IY) |- - - - - -| | + | jp (iy) |- - - - - -| |
</ | </ | ||
- | |||
- | | + | |
- | los números de 16 bits, por lo que a la hora de ensamblar un salto como " | + | |
< | < | ||
- | C3 50 C3 | + | C3 50 C3 |
</ | </ | ||
Línea 519: | Línea 496: | ||
< | < | ||
- | | + | jp 50 C3 -> |
</ | </ | ||
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. | ||
- | | + | |
- | + | ||
\\ | \\ | ||
===== Saltos relativos incondicionales: | ===== Saltos relativos incondicionales: | ||
- | | + | |
- | 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 " | + | **jr** son las siglas de " |
- | 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 | + | instrucción |
- | memoria alrededor de la posición actual (una vez decodificada la | + | |
- | instrucción | + | |
- | El argumento de JR no es pues un valor numérico | + | El argumento de '' |
- | 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 " | + | |
- | atrás: | + | |
- | | + | |
< | < | ||
- | | + | jr $+25 ; Saltar adelante 25 bytes: PC = PC+25 |
- | JR $-100 ; Saltar atrás 100 bytes: | + | jr $-100 ; Saltar atrás 100 bytes: |
</ | </ | ||
- | | + | |
- | posiciones y hacer referencia de una forma más sencilla a posiciones | + | |
en nuestro programa: | en nuestro programa: | ||
- | | + | |
- | + | ||
<code z80> | <code z80> | ||
- | | + | |
- | ORG 50000 | + | ORG 50000 |
bucle: | bucle: | ||
- | INC A | + | inc a |
- | | + | |
- | | + | |
+ | |||
+ | ret ; Esto nunca se ejecutará | ||
- | RET ; Esto nunca se ejecutará | + | END |
</ | </ | ||
- | Como puede verse, el ejemplo es exactamente igual que en el caso | + | Como puede verse, el ejemplo es exactamente igual que en el caso anterior. No tenemos que utilizar el carácter |
- | anterior. No tenemos que utilizar el carácter $ (posición actual de | + | ensamblado) porque al hacer uso de etiquetas es el ensamblador quien se encarga de traducir la etiqueta a un desplazamiento de 8 bits y |
- | ensamblado) porque al hacer uso de etiquetas es el ensamblador quien | + | |
- | se encarga de traducir la etiqueta a un desplazamiento de 8 bits y | + | |
ensamblarlo. | ensamblarlo. | ||
- | | + | |
- | 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. | + | |
- | | + | |
- | y de 8 bits en complemento a dos, no podemos saltar a cualquier | + | |
- | punto del programa, sino que sólo podremos saltar a código que esté | + | |
- | cerca de la línea actual: como máximo 127 bytes por encima o por debajo de la posición actual en memoria. | + | |
Si tratamos de ensamblar un salto a una etiqueta que está más allá del alcance de un salto relativo, obtendremos un error como el siguiente: | Si tratamos de ensamblar un salto a una etiqueta que está más allá del alcance de un salto relativo, obtendremos un error como el siguiente: | ||
Línea 593: | Línea 556: | ||
</ | </ | ||
- | En ese caso, tendremos que cambiar la instrucción | + | En ese caso, tendremos que cambiar la instrucción |
- | | + | |
- | 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á, | + | |
- | son relativos a " | + | |
- | en 60000 en lugar de en 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: | + | |
- | programar rutinas reubicables y JP no. | + | |
- | (Nota: se dice que una rutina es reubicable cuando estando programada | + | Se dice que una rutina es reubicable cuando estando programada a partir de una determinada dirección de memoria, podemos copiar |
- | a partir de una determinada dirección de memoria, podemos copiar | + | |
- | rutina a otra dirección y sus saltos funcionarán correctamente | + | |
- | por no ser absolutos). | + | |
- | | + | |
- | "// | + | |
- | podías copiar la rutina en cualquier lugar de la memoria y llamarla, | + | |
- | dado que el autor de la misma había utilizado sólo saltos relativos | + | |
- | y no absolutos, por lo que daría igual la posición de memoria en que la POKEaramos. | + | |
- | En nuestro caso, al usar un programa ensamblador en lugar de | + | En nuestro caso, al usar un programa ensamblador en lugar de simplemente disponer de las rutinas en código máquina (ya ensambladas) que nos mostraba microhobby, no se nos plantearán esos problemas, dado que nosotros podemos usar etiquetas y copiar cualquier porción del código a dónde queramos de nuestro programa. Aquellas rutinas etiquetadas como " |
- | simplemente disponer de las rutinas en código máquina (ya ensambladas) | + | |
- | que nos mostraba microhobby, no se nos plantearán esos problemas, | + | |
- | dado que nosotros podemos usar etiquetas y copiar cualquier porción | + | |
- | del código a dónde queramos de nuestro programa. Aquellas rutinas etiquetadas como " | + | |
- | | + | |
- | 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 " | + | |
- | las referencias a las etiquetas que usamos. | + | |
- | Esta facilidad de trabajo contrasta con las dificultades que tenían | + | 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), |
- | 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), | + | |
- | (JP A_mayor_que_B) utilizaban directamente direcciones en memoria. | + | |
- | E imaginad el trabajo que suponía mantener un listado en papel todas | + | 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. |
- | 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. | + | |
- | | + | |
- | la misma que para JP: nula. | + | |
- | < | + | < |
- | | + | Flags |
| | ||
| | ||
- | | + | |
| | ||
Línea 655: | Línea 584: | ||
- | | + | |
Línea 661: | Línea 590: | ||
===== Saltos condicionales con los flags ===== | ===== Saltos condicionales con los flags ===== | ||
- | Ya hemos visto la forma de realizar saltos incondicionales. A continuación | + | Ya hemos visto la forma de realizar saltos incondicionales. A continuación veremos cómo realizar los saltos (ya sean absolutos con '' |
- | veremos cómo realizar los saltos (ya sean absolutos con JP o relativos | + | |
- | con JR) //de acuerdo a unas determinadas condiciones// | + | |
- | Las instrucciones condicionales disponibles trabajan con el estado | + | Las instrucciones condicionales disponibles trabajan con el estado de los flags del registro F, y son: |
- | de los flags del registro F, y son: | + | |
\\ | \\ | ||
- | **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** | + | **jp z, direccion** |
- | **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** | + | **jp c, direccion** |
- | **JP PO, direccion** : Salta si el indicador de paridad/ | + | **jp po, direccion** : Salta si el indicador de paridad/ |
- | **JP PE, direccion** : Salta si el indicador de paridad/ | + | **jp pe, direccion** : Salta si el indicador de paridad/ |
- | **JP P, direccion** | + | **jp p, direccion** |
- | **JP M, direccion** | + | **jp m, direccion** |
\\ | \\ | ||
- | **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.\\ |
\\ | \\ | ||
- | Donde " | + | Donde " |
- | de 8 bits con signo -127 a +127. | + | |
- | | + | |
- | a considerando el resultado de la operación anterior en complemento a | + | |
- | dos). | + | |
Así, supongamos el siguiente programa: | Así, supongamos el siguiente programa: | ||
<code z80> | <code z80> | ||
- | JP Z, destino | + | jp z, destino |
- | LD A, 10 | + | ld a, 10 |
destino: | destino: | ||
- | NOP | + | nop |
</ | </ | ||
- | | + | |
- | aunque también habríamos podido especificar directamente una dirección como | + | |
- | por ejemplo 50004). | + | |
- | | + | |
- | | + | * Si el flag Z está activado (a uno), saltamos a " |
- | * 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, | + | En BASIC, |
<code basic> | <code basic> | ||
Línea 715: | Línea 636: | ||
</ | </ | ||
- | | + | |
<code basic> | <code basic> | ||
Línea 721: | Línea 642: | ||
</ | </ | ||
- | Con estas instrucciones podemos realizar saltos condicionales en función del | + | Con estas instrucciones podemos realizar saltos condicionales en función del estado de los flags o indicadores del registro F: podemos saltar si el resultado de una operación es cero, si no es cero, si hubo acarreo, si no lo hubo... |
- | estado de los flags o indicadores del registro F: podemos saltar si el resultado | + | |
- | de una operación es cero, si no es cero, si hubo acarreo, si no lo hubo... | + | |
- | Y el lector se preguntará: | + | Y el lector se preguntará: |
- | función de los flags? Pues la respuesta es: bien usados, lo tiene | + | |
- | para todo tipo de tareas: | + | |
<code z80> | <code z80> | ||
- | | + | |
- | LD A, 100 | + | ld a, 100 |
bucle: | bucle: | ||
- | NOP | + | nop |
- | + | ||
- | DEC A | + | dec a ; Decrementamos A. |
- | | + | ; Cuando A sea cero, Z se pondrá a 1 |
- | JR NZ, bucle | + | |
- | LD A, 200 ; Aquí llegaremos cuando Z sea 1 (A valga 0) | + | jr nz, bucle ; Mientras Z=0, repetir el bucle |
- | ; resto del programa | + | |
+ | ld a, 200 | ||
+ | |||
+ | | ||
</ | </ | ||
- | Es decir: cargamos en A el valor 100, y tras ejecutar la instrucción | + | Es decir: cargamos en A el valor 100, y tras ejecutar la instrucción |
- | un "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 | + | Y como el flag Z es cero (NON ZERO = no activado el flag zero) la instrucción |
- | instrucción | + | |
- | 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 | + | Tras repetirse 100 veces el proceso, llegará un momento en que A valga cero tras el '' |
- | cero tras el "DEC A". En ese momento se activará el flag de ZERO con lo | + | |
- | que la instrucción | + | |
- | con el "LD A, 200". | + | |
- | | + | Así pues, **acabamos implementar un bucle gracias a los flags y las instrucciones condicionales**. |
- | comparación de igualdad: | + | |
+ | Veamos otro ejemplo más gráfico: vamos a implementar en ASM una comparación de igualdad: | ||
<code basic> | <code basic> | ||
- | IF A=B THEN GOTO iguales ELSE GOTO distintos | + | IF A=B THEN GOTO iguales ELSE GOTO distintos |
</ | </ | ||
En ensamblador: | En ensamblador: | ||
- | + | ||
<code z80> | <code z80> | ||
- | SUB B | + | sub b |
- | JR Z, iguales | + | jr z, iguales |
- | JR NZ, distintos | + | jr nz, distintos |
- | | + | |
iguales: | iguales: | ||
- | ;;; (código) | + | |
- | JR seguir | + | |
- | | + | |
distintos: | distintos: | ||
- | ;;; (código) | + | |
- | JR seguir | + | ;jr seguir |
+ | ; ya continuamos en "seguir" | ||
seguir: | seguir: | ||
</ | </ | ||
- | | + | O, invirtiendo el orden de la comparación, |
- | Para comparar A con B los restamos (A=A-B). Si el resultado de la resta | + | <code z80> |
- | es cero, es porque A era igual a B. Si no es cero, es que eran distintos. | + | sub b ; A = A-B |
- | Y utilizando el flag de Zero con JP Z y JP NZ podemos detectar esa | + | jr nz, distintos |
- | diferencia. | + | ; Si Z=1, seguimos aqui, ya en " |
- | | + | iguales: |
- | 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. | + | ;;; (código) |
+ | jr seguir | ||
- | Para finalizar, un detalle sobre **DEC+JR**: La combinación **DEC B / JR NZ** | + | distintos: |
- | se puede sustituir (es más eficiente, y más sencillo) por el comando | + | ;;; (código) |
- | **DJNZ**, que literalmente significa " | + | |
- | a < | + | seguir: |
+ | </ | ||
+ | |||
+ | | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | | ||
+ | |||
+ | Para finalizar, un detalle sobre **DEC'' | ||
\\ | \\ | ||
- | **DJNZ direccion** | + | **djnz direccion** |
| | ||
- | Esta instrucción se usa habitualmente en bucles (usando B como iterador | + | <code z80> |
- | del mismo) y, al igual que JP y JR, no afecta al estado de los flags: | + | bucle: |
+ | ; (pasos a repertir) | ||
+ | |||
+ | djnz bucle ; Decrementar B, salta si distinto de 0 | ||
+ | </ | ||
+ | |||
+ | Esta instrucción se usa habitualmente en bucles (usando B como iterador del mismo) y, al igual que jp y jr, no afecta al estado de los flags: | ||
< | < | ||
- | | + | Flags |
| | ||
| | ||
- | |JP COND, NN |- - - - - -| | + | |jp COND, NN |- - - - - -| |
- | |JR COND, d |- - - - - -| | + | |jr COND, d |- - - - - -| |
- | |DJNZ d |- - - - - -| | + | |djnz d |- - - - - -| |
</ | </ | ||
- | 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), | + | El argumento de salto de '' |
<code z80> | <code z80> | ||
- | DEC B ; Decrementar B, afecta a los flags | + | bucle: |
- | JP NZ, direccion | + | ; (pasos a repertir) |
+ | |||
+ | dec b ; Decrementar B, afecta a los flags | ||
+ | jp nz, direccion | ||
</ | </ | ||
- | DJNZ trabaja con el registro B como contador de repeticiones, | + | '' |
<code z80> | <code z80> | ||
- | DEC BC ; Decrementamos BC -> no afecta a los flags | + | bucle: |
- | LD A, B ; Cargamos B en A | + | ; (pasos a repetir) |
- | OR C ; Hacemos OR a de A y C (de B y C) | + | |
- | JR NZ, direccion | + | dec bc ; Decrementamos BC -> no afecta a los flags |
+ | ld a, b ; Cargamos B en A | ||
+ | or c ; Hacemos OR a de A y C (de B y C) | ||
+ | jr nz, bucle | ||
</ | </ | ||
Línea 836: | Línea 772: | ||
==== Comparaciones de 8 bits ==== | ==== Comparaciones de 8 bits ==== | ||
- | Para realizar comparaciones (especialmente de igualdad, mayor que y menor que) | + | Para realizar comparaciones (especialmente de igualdad, mayor que y menor que) utilizaremos la instrucción |
- | utilizaremos la instrucción | + | |
<code z80> | <code z80> | ||
- | | + | cp origen |
</ | </ | ||
- | Donde " | + | Donde " |
- | directo, (HL), (IX+d) o (IY+d). | + | |
- | Al realizar una instrucción | + | Al realizar una instrucción |
- | operación " | + | |
- | Lo que sí que hace es //alterar el estado de los flags// de acuerdo al | + | |
- | resultado de la operación. | + | |
- | | + | |
- | resta, perdiendo por tanto el valor de A: | + | |
<code z80> | <code z80> | ||
- | SUB B | + | sub b |
- | JR Z, iguales | + | jr z, iguales |
- | JR NZ, distintos | + | jr nz, distintos |
</ | </ | ||
- | | + | |
- | de A (por la resta): | + | |
<code z80> | <code z80> | ||
- | CP B ; Flags = estado(A-B) | + | cp b ; Flags = estado(A-B) |
- | JR Z, iguales | + | jr z, iguales |
- | JR NZ, distintos | + | jr nz, distintos |
</ | </ | ||
- | ¿Qué nos permite esto? Aprovechando todos los flags del registro F | + | ¿Qué nos permite esto? Aprovechando todos los flags del registro F (flag de acarreo, flag de zero, etc), realizar comparaciones como las siguientes: |
- | (flag de acarreo, flag de zero, etc), realizar comparaciones como | + | |
- | las siguientes: | + | |
<code z80> | <code z80> | ||
- | | + | |
- | LD B, 5 | + | ld b, 5 |
- | LD A, 3 | + | ld a, 3 |
+ | |||
+ | cp b ; Flags = estado(A-B) | ||
+ | jp z, A_Igual_que_B | ||
+ | jp nc, A_Mayor_o_igual_que_B | ||
+ | jp c, A_Menor_que_B | ||
- | CP B ; Flags = estado(A-B) | ||
- | JP Z, A_Igual_que_B | ||
- | JP NC, A_Mayor_o_igual_que_B | ||
- | JP C, A_Menor_que_B | ||
- | | ||
A_Mayor_que_B: | A_Mayor_que_B: | ||
- | | + | |
- | | + | |
A_Menor_que_B: | A_Menor_que_B: | ||
- | | + | |
- | | + | |
A_Igual_que_B: | A_Igual_que_B: | ||
- | | + | |
- | + | ||
fin: | fin: | ||
- | | + | |
</ | </ | ||
Línea 901: | 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 | + | cp b |
- | JR Z, A_Igual_a_B | + | jr z, A_Igual_a_B |
- | CP C | + | cp c |
- | JR Z, A_Igual_a_C | + | jr z, A_Igual_a_C |
- | | + | |
A_Igual_a_B: | A_Igual_a_B: | ||
- | | + | |
- | | + | |
A_Igual_a_C: | A_Igual_a_C: | ||
- | | + | |
Fin: | Fin: | ||
- | | + | |
</ | </ | ||
Línea 924: | Línea 852: | ||
< | < | ||
- | | + | Flags |
| | ||
| | ||
- | |CP s |* * * V 1 *| | + | |cp s |* * * V 1 *| |
</ | </ | ||
- | El flag " | + | El flag " |
- | operación efectuada es una resta. | + | |
\\ | \\ | ||
==== Comparaciones de 16 bits ==== | ==== Comparaciones de 16 bits ==== | ||
- | | + | |
- | Si lo que queremos comparar es un registro con otro, podemos hacerlo mediante un CP de su parte alta y su parte baja. Por ejemplo, para comparar HL con DE: | + | Si lo que queremos comparar es un registro con otro, podemos hacerlo mediante un '' |
<code z80> | <code z80> | ||
- | | + | |
- | LD A, H | + | ld a, h |
- | CP D | + | cp d |
- | JR NZ, no_iguales | + | jr nz, no_iguales |
- | LD A, L | + | ld a, l |
- | CP E | + | cp e |
- | JR NZ, no_iguales | + | jr nz, no_iguales |
iguales: | iguales: | ||
- | | + | |
no_iguales: | no_iguales: | ||
- | | + | |
</ | </ | ||
- | |||
Para comparar si el valor de un registro es igual a un valor numérico inmediato (introducido directamente en el código de programa), utilizaríamos el siguiente código: | Para comparar si el valor de un registro es igual a un valor numérico inmediato (introducido directamente en el código de programa), utilizaríamos el siguiente código: | ||
<code z80> | <code z80> | ||
- | ;;; Comparacion 16 bits de HL y VALOR_NUMERICO (inmediato) | + | |
- | | + | ;;; VALOR_NUMERICO puede ser cualquier valor de 0 a 65535 |
- | LD A, H | + | ld a, h |
- | | + | |
- | JR NZ, no_iguales | + | jr nz, no_iguales |
- | LD A, L | + | ld a, l |
- | | + | |
- | JR NZ, no_iguales | + | jr nz, no_iguales |
iguales: | iguales: | ||
- | | + | |
no_iguales: | no_iguales: | ||
- | | + | |
</ | </ | ||
- | |||
\\ | \\ | ||
===== Consideraciones de las condiciones ===== | ===== Consideraciones de las condiciones ===== | ||
- | A la hora de utilizar instrucciones condicionales hay que tener en | + | 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 |
- | cuenta que no todas las instrucciones afectan a los flags. Por ejemplo, | + | |
- | la instrucción | + | |
- | Si intentamos montar un bucle mediante | + | |
- | del mismo, ya que DEC BC no afecta al flag de zero. | + | |
<code z80> | <code z80> | ||
- | | + | |
bucle: | bucle: | ||
(...) | (...) | ||
- | | + | |
- | | + | |
</ | </ | ||
- | Para evitar estas situaciones necesitamos conocer la afectación de | + | Para evitar estas situaciones necesitamos conocer la afectación de los flags ante cada instrucción, |
- | los flags ante cada instrucción, | + | |
- | tablas que os hemos proporcionado. | + | |
- | | + | |
- | (de nuevo) de los flags y de los resultados de las operaciones lógicas | + | |
- | (y sus efectos sobre el registro F). Como ya vimos al tratar la instrucción DJNZ, podemos comprobar si un registro de 16 bits vale 0 realizando un OR entre la parte alta y la parte baja del mismo. Esto sí afectará a los flags y permitirá realizar el salto condicional: | + | |
<code z80> | <code z80> | ||
- | | + | |
bucle: | bucle: | ||
(...) | (...) | ||
- | | + | |
- | | + | |
- | | + | |
; 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. | ||
- | | + | |
</ | </ | ||
- | Más detalles sobre los saltos condicionales: | + | Más detalles sobre los saltos condicionales: |
- | 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/ | + | Los saltos por Paridad/ |
- | 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. | + | ===== Retrasando el salto sin alterar los flags ===== |
- | + | ||
- | | + | No estamos obligados a utilizar los flags para realizar un salto condicional justo después de utilizar '' |
- | resta, | + | |
- | 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. | + | Por ejemplo, '' |
+ | |||
+ | Supongamos que queremos poner en A el valor ' | ||
+ | |||
+ | No es necesario que hagamos: | ||
+ | |||
+ | <code z80> | ||
+ | cp 0 | ||
+ | jr z, es_cero | ||
+ | ld a, ' | ||
+ | jr continuar | ||
+ | |||
+ | es_cero: | ||
+ | ld a, ' | ||
+ | |||
+ | continuar: | ||
+ | ;;; Aqui A vale ' | ||
+ | </ | ||
+ | |||
+ | Directamente, | ||
+ | |||
+ | <code z80> | ||
+ | cp 0 | ||
+ | ld a, ' | ||
+ | jr z, es_cero | ||
+ | ld a, ' | ||
+ | |||
+ | es_cero: | ||
+ | ;;; Aqui A vale ' | ||
+ | </ | ||
\\ | \\ | ||
===== La importancia de la probabilidad de salto ===== | ===== La importancia de la probabilidad de salto ===== | ||
- | Ante una instrucción condicional, | + | Ante una instrucción condicional, |
- | 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. | + | |
- | | + | |
- | interesante el pararse a pensar cuál puede ser el caso con más probabilidades | + | |
- | de ejecución//, | + | |
- | LO QUE SE PRODUCE EL SALTO//" | + | |
- | NO SALTO Y SIGO//" | + | |
- | Por ejemplo, ante un "JP Z, direccion", el microprocesador tardará 10 ciclos | + | Por ejemplo, ante un '' |
- | 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). | + | |
| | ||
Línea 1062: | Línea 995: | ||
Valor_Mayor_Que_250: | Valor_Mayor_Que_250: | ||
- | + | cp 250 ; Comparamos A con 250 | |
- | | + | jp c, A_menor_que_250 |
- | JP C, A_menor_que_250 | + | 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 |
</ | </ | ||
En el ejemplo anterior se produce el salto si A es menor que 250 (10 t-estados) y no se produce si A es mayor que 250 (1 t-estado). | En el ejemplo anterior se produce el salto si A es menor que 250 (10 t-estados) y no se produce si A es mayor que 250 (1 t-estado). | ||
- | | + | |
- | Lo normal es que, ante datos aleatorios, haya más probabilidad de encontrar | + | Lo normal es que, ante datos aleatorios, haya más probabilidad de encontrar datos del segundo caso (0-250) que del primero (250-255), simplemente por el hecho de que del primer caso hay 250 probabilides de 255, mientras que del segundo hay 5 probabilidades de 255. |
- | datos del segundo caso (0-250) que del primero (250-255), simplemente por el | + | |
- | hecho de que del primer caso hay 250 probabilides de 255, mientras que del segundo hay 5 | + | |
- | probabilidades de 255. | + | |
- | En tal caso, la rutina debería organizarse de | + | En tal caso, la rutina debería organizarse de forma que la comparación realice el salto cuando encuentre un dato mayor de 250, dado que ese supuesto se dará menos veces. Si lo hicieramos a la inversa, se saltaría más veces y la rutina tardaría más en realizar el mismo |
- | forma que la comparación realice el salto cuando encuentre un dato mayor | + | |
- | de 250, dado que ese supuesto se dará menos veces. Si lo hicieramos a la | + | |
- | inversa, se saltaría más veces y la rutina tardaría más en realizar el mismo | + | |
trabajo. | trabajo. | ||
Línea 1096: | Línea 1022: | ||
Valor_Mayor_Que_250: | Valor_Mayor_Que_250: | ||
- | + | cp 250 ; Comparamos A con 250 | |
- | | + | jp nc, A_mayor_que_250 |
- | JP NC, A_mayor_que_250 | + | 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 |
</ | </ | ||
Eso hace que haya más posibilidades de no saltar que de saltar, es decir, de emplear un ciclo de procesador y no 10 para la mayoría de las ejecuciones. | Eso hace que haya más posibilidades de no saltar que de saltar, es decir, de emplear un ciclo de procesador y no 10 para la mayoría de las ejecuciones. | ||
+ | |||
+ | |||
+ | \\ | ||
+ | ===== Bucles hasta N o bucles hasta 0 ===== | ||
+ | |||
+ | Gracias a la instrucción '' | ||
+ | |||
+ | < | ||
+ | For i=1 TO 20 | ||
+ | | ||
+ | NEXT I | ||
+ | </ | ||
+ | |||
+ | De la siguiente forma: | ||
+ | |||
+ | <code z80> | ||
+ | ld a, 1 ; Valor Inicial | ||
+ | bucle: | ||
+ | |||
+ | ; Codigo a repetir 20 veces | ||
+ | |||
+ | inc a | ||
+ | cp 20 ; Valor final | ||
+ | jr nz, bucle | ||
+ | </ | ||
+ | |||
+ | Dado que el registro A es vital en muchas operaciones en el Z80 (es el operando único en algunos opcodes), no es una buena idea utilizarlo como contador de un bucle, ya que en ese caso tendremos que hacer haciendo copias (en otros registros, o en la pila) para recuperarlo antes de hacer el '' | ||
+ | |||
+ | Además, sólo nos permitiría realizar bucles de 8 bits. | ||
+ | |||
+ | Para eso, el registro utilizando habitualmente como contador de bucles es **B** para cuentas de 8 bits, y **BC** para cuentas de 16 bits. | ||
+ | |||
+ | Eso convertiría nuestro bucle anterior en: | ||
+ | |||
+ | <code z80> | ||
+ | ld b, 1 | ||
+ | bucle: | ||
+ | |||
+ | ; Codigo a repetir 20 veces | ||
+ | |||
+ | inc b | ||
+ | ld a, b | ||
+ | cp 20 | ||
+ | jr NZ bucle | ||
+ | </ | ||
+ | |||
+ | No obstante, nos obliga a copiar B en A para poder hacer el '' | ||
+ | |||
+ | | ||
+ | |||
+ | | ||
+ | |||
+ | <code z80> | ||
+ | ld b, 20 | ||
+ | bucle: | ||
+ | |||
+ | ; Codigo a repetir 20 veces | ||
+ | |||
+ | dec b | ||
+ | jr nz, bucle | ||
+ | </ | ||
+ | |||
+ | Usando '' | ||
+ | |||
+ | <code z80> | ||
+ | ld b, 20 | ||
+ | bucle: | ||
+ | |||
+ | ; Codigo a repetir 20 veces | ||
+ | |||
+ | djnz, bucle | ||
+ | </ | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | <code z80> | ||
+ | ld bc, 10000 ; veces a iterar | ||
+ | |||
+ | bucle: | ||
+ | |||
+ | ; ... código de nuestro bucle ... | ||
+ | |||
+ | dec bc ; Decrementamos el contador | ||
+ | ld a, b ; A = B | ||
+ | or c ; A = A or c = B or c | ||
+ | jp nz, bucle ; Si B or c == 0 => es que BC = 0 | ||
+ | </ | ||
+ | |||
+ | En el ejemplo anterior, decrementamos el valor de BC y hacemos un '' | ||
+ | |||
+ | \\ | ||
+ | ===== Desenrollando bucles ===== | ||
+ | |||
+ | 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. | ||
+ | |||
+ | 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 ('' | ||
+ | |||
+ | Este proceso se llama **desenrollar el bucle** y consiste en, básicamente, | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code z80> | ||
+ | ORG 50000 | ||
+ | |||
+ | call ROM_CLS | ||
+ | |||
+ | ld hl, $4000 | ||
+ | ld a, $ff | ||
+ | ld b, 8 ; Repetir 8 veces | ||
+ | |||
+ | bucle: | ||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | djnz bucle | ||
+ | |||
+ | ret | ||
+ | |||
+ | ROM_CLS EQU $0daf | ||
+ | |||
+ | END 50000 | ||
+ | </ | ||
+ | |||
+ | En pantalla aparecerá lo siguiente: | ||
+ | |||
+ | \\ | ||
+ | {{ cursos: | ||
+ | \\ | ||
+ | |||
+ | El programa resultante ocupa 15 bytes hasta el '' | ||
+ | |||
+ | \\ | ||
+ | < | ||
+ | $ hexdump -C bucle.bin | ||
+ | 00000000 | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <code z80> | ||
+ | ORG 50000 | ||
+ | |||
+ | call ROM_CLS | ||
+ | |||
+ | ld hl, $4000 | ||
+ | ld a, $ff | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | |||
+ | ; El ultimo inc hl no es necesario ya que no vamos | ||
+ | ; a escribir en el siguiente bloque de pixeles. | ||
+ | |||
+ | ret | ||
+ | |||
+ | ROM_CLS EQU $0daf | ||
+ | |||
+ | END 50000 | ||
+ | </ | ||
+ | |||
+ | Este código produce el mismo resultado en pantalla, pero tiene varias ventajas: | ||
+ | |||
+ | * 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). | ||
+ | |||
+ | * El '' | ||
+ | |||
+ | * El último '' | ||
+ | |||
+ | 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. | ||
+ | |||
+ | Sin embargo, el haber desenrollado el bucle tiene una desventaja, un coste: | ||
+ | |||
+ | * Nuestro programa de 15 bytes ocupa ahora 24 bytes, ya que hemos tenido que repetir código 8 veces: | ||
+ | |||
+ | \\ | ||
+ | < | ||
+ | $ hexdump -C bucle_desenrollado.bin | ||
+ | 00000000 | ||
+ | 00000010 | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | Como puede apreciarse, ahora tenemos repetidas varias veces las instrucciones '' | ||
+ | |||
+ | 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 " | ||
+ | |||
+ | Cabe destacar que los programas ensambladores suelen disponer de directivas para **repetir código** sin tener que escribirlo varias veces. | ||
+ | |||
+ | En **pasmo** tenemos las directivas '' | ||
+ | |||
+ | En **sjasmplus** se utiliza '' | ||
+ | |||
+ | <code z80> | ||
+ | ; En pasmo: REPT - ENDM | ||
+ | ld a, $ff | ||
+ | |||
+ | REPT 8 | ||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | ENDM | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | </ | ||
+ | |||
+ | <code z80> | ||
+ | ; En sjasmplus: REPT - ENDR | ||
+ | ld a, $ff | ||
+ | |||
+ | REPT 8 | ||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | inc hl ; Siguiente bloque de pixeles | ||
+ | ENDR | ||
+ | |||
+ | ld (hl), a ; Escribir valor en pantalla | ||
+ | </ | ||
+ | |||
+ | Es una lástima que ambas directivas no sean la misma y podamos escribir código compatible con ambos ensambladores, | ||
+ | |||
\\ | \\ | ||
===== Instrucciones de comparacion repetitivas ===== | ===== Instrucciones de comparacion repetitivas ===== | ||
- | Para acabar con las instrucciones de comparación vamos a ver las | + | Para acabar con las instrucciones de comparación vamos a ver las instrucciones de comparación repetitivas. Son parecidas a '' |
- | instrucciones de comparación repetitivas. Son parecidas a CP, pero | + | |
- | trabajan (igual que LDI, LDIR, LDD y LDDR) con HL y BC para realizar | + | |
- | las comparaciones con la memoria: son **CPI, CPD, CPIR** y **CPDR**. | + | |
- | | + | |
\\ | \\ | ||
\\ | \\ | ||
- | **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 1132: | Línea 1300: | ||
* Se decrementa BC. | * Se decrementa BC. | ||
- | | + | |
< | < | ||
- | | + | cpi = cp (hl) |
- | INC HL | + | inc hl |
- | DEC BC | + | dec bc |
</ | </ | ||
\\ | \\ | ||
- | **CPD:** | + | **cpd:** |
\\ | \\ | ||
- | Su instrucción " | + | Su instrucción " |
- | forma, pero decrementando HL: | + | |
< | < | ||
- | | + | cpd = cp (hl) |
- | DEC HL | + | dec hl |
- | DEC BC | + | dec bc |
</ | </ | ||
- | Y el pequeño matiz: así como CP [HL] afecta al indicador C de Carry, //CPI y CPD//, | + | Y el pequeño matiz: así como '' |
- | aunque realizan esa operación intermedia, //no lo afectan// | + | |
- | + | Las instrucciones | |
- | Las instrucciones | + | |
- | 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, | + | |
- | 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). | + | |
\\ | \\ | ||
\\ | \\ | ||
- | **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. | ||
* El resultado de la resta no se almacena en ningún sitio. | * El resultado de la resta no se almacena en ningún sitio. | ||
- | * Los flags resultan afectados por la comparación: | + | * Los flags resultan afectados por la comparación: |
* Si A==(HL), se pone a 1 el flag de Zero (si no es igual se pone a 0). | * Si A==(HL), se pone a 1 el flag de Zero (si no es igual se pone a 0). | ||
* Si BC==0000, se pone a 0 el flag Parity/ | * Si BC==0000, se pone a 0 el flag Parity/ | ||
Línea 1178: | 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. | + | |
Como ya hemos comentado, muchos flags se ven afectados: | Como ya hemos comentado, muchos flags se ven afectados: | ||
< | < | ||
- | Flags | + | Flags |
| | ||
| | ||
- | |CPI |* * * * 1 -| | + | |cpi |* * * * 1 -| |
- | |CPD |* * * * 1 -| | + | |cpd |* * * * 1 -| |
- | |CPIR |* * * * 1 -| | + | |cpir |* * * * 1 -| |
- | |CPDR |* * * * 1 -| | + | |cpdr |* * * * 1 -| |
</ | </ | ||
- | Un ejemplo de uso de un CP repetitivo es realizar búsquedas de | + | Un ejemplo de uso de un '' |
- | un determinado valor en memoria. Supongamos que deseamos buscar | + | |
- | la primera aparición del valor " | + | |
- | dirección 20000, y hasta la dirección 30000, es decir, encontrar la | + | |
- | dirección de la primera celdilla de memoria entre 20000 y 30000 que | + | |
- | contenga el valor 123. | + | |
- | | + | |
<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 |
</ | </ | ||
Línea 1214: | Línea 1368: | ||
< | < | ||
- | | + | HL = 20000 |
- | BC = 10000 | + | BC = 10000 |
- | A = 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 | + | Si BC==0 |
HL = HL+1 | HL = HL+1 | ||
BC = BC-1 | BC = BC-1 | ||
- | Fin_de_CPIR: | + | Fin_de_cpir: |
</ | </ | ||
- | Con esto, si la celdilla 15000 contiene el valor " | + | Con esto, si la celdilla 15000 contiene el valor " |
- | 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 " | + | |
- | BC tendremos restado el número de iteraciones del " | + | |
- | Si no se encuentra ninguna aparición de " | + | Si no se encuentra ninguna aparición de " |
- | porque el " | + | |
- | al igual que Z, indicando que se finalizó el CPIR y no se encontró nada. | + | |
- | | + | |
- | 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). | + | |
\\ | \\ | ||
===== Un ejemplo con CPIR ===== | ===== Un ejemplo con CPIR ===== | ||
- | + | Veamos un ejemplo práctico con '' | |
- | Veamos un ejemplo práctico con CPIR. El código que veremos a continuación | + | |
- | realiza una búsqueda de un determinado carácter ASCII en una cadena de texto: | + | |
<code z80> | <code z80> | ||
; Principio del programa | ; Principio del programa | ||
- | ORG 50000 | + | |
- | LD HL, texto | + | ld hl, texto ; Inicio de la busqueda |
- | LD A, ' | + | ld a, ' |
- | LD BC, 100 | + | ld bc, 100 ; Número de bytes donde buscar |
- | CPIR ; Realizamos la búsqueda | + | |
- | + | ||
- | JP NZ, No_Hay | + | |
- | ; el flag de Z estará a cero. | + | |
- | | + | jp nz, No_Hay |
- | DEC HL ; Decrementamos HL para apuntar al byte | + | ; el flag de Z estará a cero. |
- | ; encontrado en memoria. | + | |
- | LD BC, texto | + | ; Si seguimos por aquí es que se encontró |
- | | + | dec hl ; |
- | | + | ; encontrado en memoria. |
- | | + | |
- | ; = (posicion encontrada) - (inicio cadena) | + | |
- | ; = posición de ' | + | |
- | LD B, H | + | ld bc, texto |
- | LD C, L ; BC = HL | + | scf |
- | + | ccf ; Ponemos el carry flag a 0 (scf+ccf) | |
- | RET | + | sbc hl, bc ; HL = HL - BC |
+ | ; = (posicion encontrada) - (inicio cadena) | ||
+ | ; = posición de ' | ||
+ | |||
+ | ld b, h | ||
+ | ld c, l ; BC = HL | ||
+ | |||
+ | | ||
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." | ||
- | | + | ; Fin del programa |
- | END | + | |
</ | </ | ||
- | Lo compilamos con "**pasmo < | + | Lo compilamos con '' |
- | "**PRINT AT 10,10 ; USR 50000**". | + | |
- | En pantalla aparecerá el valor 12: | + | En pantalla aparecerá el valor "12": |
+ | \\ | ||
{{ cursos: | {{ cursos: | ||
+ | \\ | ||
- | | + | |
- | la cadena de texto. La hemos obtenido de la siguiente forma: | + | |
* Hacemos HL = posición de memoria donde empieza la cadena. | * Hacemos HL = posición de memoria donde empieza la cadena. | ||
* Hacemos A = ' | * Hacemos A = ' | ||
- | * Ejecutamos un CPIR | + | * Ejecutamos un '' |
* En HL obtendremos la posición absoluta + 1 donde se encuentra el carácter ' | * En HL obtendremos la posición absoluta + 1 donde se encuentra el carácter ' | ||
* Decrementamos HL para que apunte a la ' | * Decrementamos HL para que apunte a la ' | ||
* Realizamos la resta de Posicion(' | * Realizamos la resta de Posicion(' | ||
- | * 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 '' |
- | | + | |
- | ser el equivalente a "HL = HL - BC", y se tiene que hacer de esta forma | + | |
- | porque no existe | + | |
< | < | ||
- | SUB HL, BC = | + | sub hl, bc = |
- | | + | |
- | | + | |
- | LD BC, HL | + | ld bc, hl |
- | | + | |
</ | </ | ||
Línea 1325: | Línea 1465: | ||
===== En resumen ===== | ===== En resumen ===== | ||
- | En este capítulo hemos aprendido a utilizar todas las funciones condicionales y de salto de que nos provee el Z80. Será necesario comprender perfectamente el funcionamiento de los flags para poder desarrollar código ensamblador. Para ello recordemos que nuestra librería **utils.asm** contiene las rutinas | + | En este capítulo hemos aprendido a utilizar todas las funciones condicionales y de salto de que nos provee el Z80. Será necesario comprender perfectamente el funcionamiento de los flags para poder desarrollar código ensamblador. Para ello recordemos que nuestra librería **utils.asm** contiene las rutinas |
- | En el próximo trataremos la PILA (Stack) del Spectrum, gracias a la cual podremos implementar en ensamblador el equivalente a | + | En el próximo trataremos la PILA (Stack) del Spectrum, gracias a la cual podremos implementar en ensamblador el equivalente a '' |
- | GOSUB/ | + | |
\\ | \\ | ||
Línea 1335: | Línea 1474: | ||
* {{cursos: | * {{cursos: | ||
* {{cursos: | * {{cursos: | ||
- | * {{cursos: | + | * {{cursos: |
* {{cursos: | * {{cursos: | ||
- | * {{cursos: | + | * {{cursos: |
* {{cursos: | * {{cursos: | ||
Línea 1346: | Línea 1485: | ||
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// | ||
- | * [[http:// | + | * [[http:// |
- | * [[http:// | + | * [[http:// |
* [[http:// | * [[http:// | ||
* [[http:// | * [[http:// |