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_4 [14-01-2024 07:34] – [Utilizar el Buffer de Impresión para la pila o alojar variables] sromero | cursos:ensamblador:lenguaje_4 [22-01-2024 07:54] (actual) – [PUSH y POP] sromero | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
====== Lenguaje Ensamblador del Z80 (IV) ====== | ====== Lenguaje Ensamblador del Z80 (IV) ====== | ||
- | ====== La pila y las llamadas a subrutinas | + | ===== La pila y las llamadas a subrutinas ===== |
\\ | \\ | ||
Línea 25: | Línea 25: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
(...) ; Operamos con BC | (...) ; Operamos con BC | ||
- | | + | |
- | | + | |
- | ; (recordemos que no existe "LD HL, BC", de modo que | + | ; (recordemos que no existe "ld hl, bc", de modo que |
; lo almacenamos como HL = 0+BC | ; lo almacenamos como HL = 0+BC | ||
- | | + | |
; recuperamos el valor que tenia BC (1000). | ; recuperamos el valor que tenia BC (1000). | ||
</ | </ | ||
- | La instrucción '' | + | La instrucción '' |
La realidad es que //el Spectrum no tiene una zona de memoria especial o aislada de la RAM dedicada a la pila. En su lugar se utiliza la misma RAM// del Spectrum (0-65535). | La realidad es que //el Spectrum no tiene una zona de memoria especial o aislada de la RAM dedicada a la pila. En su lugar se utiliza la misma RAM// del Spectrum (0-65535). | ||
Línea 57: | Línea 57: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
+ | (Nota: como veremos más adelante, para poner la pila al final de la memoria en realidad hay que ponerla en $0000, pero por motivos didácticos y para simplificar la explicación, | ||
+ | |||
Si ahora hacemos: | Si ahora hacemos: | ||
<code z80> | <code z80> | ||
- | | + | |
</ | </ | ||
Línea 72: | Línea 74: | ||
< | < | ||
SP = SP - 2 = 65533 | SP = SP - 2 = 65533 | ||
- | (SP) = BC = $00FF | + | (SP) = BC = $00ff |
</ | </ | ||
Línea 80: | Línea 82: | ||
Celdilla | Celdilla | ||
| | ||
- | | + | |
SP -> 65533 $00 | SP -> 65533 $00 | ||
</ | </ | ||
Línea 87: | Línea 89: | ||
<code z80> | <code z80> | ||
- | | + | |
</ | </ | ||
Línea 94: | Línea 96: | ||
< | < | ||
SP = SP - 2 = 65531 | SP = SP - 2 = 65531 | ||
- | (SP) = DE = $AABB | + | (SP) = DE = $aabb |
</ | </ | ||
Línea 102: | Línea 104: | ||
Celdilla | Celdilla | ||
| | ||
- | | + | |
| | ||
- | | + | |
- | SP -> 65531 $BB | + | SP -> 65531 $bb |
</ | </ | ||
Línea 111: | Línea 113: | ||
<code z80> | <code z80> | ||
- | | + | |
</ | </ | ||
Línea 117: | Línea 119: | ||
< | < | ||
- | DE = (SP) = $AABB | + | DE = (SP) = $aabb |
SP = SP + 2 = 65533 | SP = SP + 2 = 65533 | ||
</ | </ | ||
Línea 126: | Línea 128: | ||
Celdilla | Celdilla | ||
| | ||
- | | + | |
SP -> 65533 $00 | SP -> 65533 $00 | ||
</ | </ | ||
- | Como podemos ver, **PUSH apila valores**, haciendo decrecer el valor de SP, mientras que **POP recupera valores**, haciendo crecer (en 2 bytes, 16 bits) el valor de SP. | + | Como podemos ver, **PUSH apila valores**, haciendo decrecer el valor de SP, mientras que **pop recupera valores**, haciendo crecer (en 2 bytes, 16 bits) el valor de SP. |
Con el objetivo de que el ejemplo fuera más comprensible, | Con el objetivo de que el ejemplo fuera más comprensible, | ||
Línea 141: | Línea 143: | ||
Así pues, podemos hacer **PUSH** y **POP** de los siguientes registros: | Así pues, podemos hacer **PUSH** y **POP** de los siguientes registros: | ||
- | * PUSH: AF, BC, DE, HL, IX, IY | + | * '' |
- | * POP : AF, BC, DE, HL, IX, IY | + | * '' |
Lo que hacen PUSH y POP, tal y como funciona la pila, es: | Lo que hacen PUSH y POP, tal y como funciona la pila, es: | ||
< | < | ||
- | PUSH xx : | + | push xx : |
| | ||
(SP) = xx | (SP) = xx | ||
- | POP xx : | + | pop xx : |
xx = (SP) | xx = (SP) | ||
SP = SP+2 | SP = SP+2 | ||
+ | </ | ||
+ | |||
+ | Visto en " | ||
+ | |||
+ | <code z80> | ||
+ | push hl = LET SP = SP-2 | ||
+ | POKE (SP+1), H | ||
+ | POKE SP, L | ||
+ | |||
+ | pop hl | ||
+ | LET H = PEEK (SP+1) | ||
+ | LET SP = SP+2 | ||
</ | </ | ||
Línea 162: | Línea 176: | ||
| | ||
| | ||
- | POP xx |- - - - - -| | + | pop xx |- - - - - -| |
- | PUSH xx |- - - - - -| | + | push xx |- - - - - -| |
</ | </ | ||
- | | + | |
\\ | \\ | ||
Línea 174: | Línea 188: | ||
\\ | \\ | ||
- | * Intercambiar valores | + | * Uno de sus usos más evidentes |
- | + | ||
- | <code z80> | + | |
- | PUSH BC ; Apilamos BC | + | |
- | PUSH DE ; Apilamos DE | + | |
- | POP BC ; Desapilamos BC | + | |
- | ; ahora BC=(valor apilado en PUSH DE) | + | |
- | POP DE ; Desapilamos DE | + | |
- | ; ahora DE=(valor apilado en PUSH BC) | + | |
- | </ | + | |
- | + | ||
- | \\ | + | |
- | * Para manipular el registro F: La instrucción '' | + | |
- | + | ||
- | * Almacenaje | + | |
\\ | \\ | ||
<code z80> | <code z80> | ||
- | | + | |
- | (código) | + | (código) |
- | | + | |
</ | </ | ||
Línea 202: | Línea 202: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
bucle: | bucle: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 216: | Línea 216: | ||
<code basic> | <code basic> | ||
- | FOR I=0 TO 20: | + | For i=0 TO 20: |
FOR J=0 TO 100: | FOR J=0 TO 100: | ||
CODIGO | CODIGO | ||
Línea 226: | Línea 226: | ||
<code z80> | <code z80> | ||
- | | + | |
bucle_externo: | bucle_externo: | ||
- | | + | |
- | | + | |
bucle_interno: | bucle_interno: | ||
(... código ...) | (... código ...) | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 243: | Línea 243: | ||
<code z80> | <code z80> | ||
- | | + | |
bucle_externo: | bucle_externo: | ||
- | | + | |
- | | + | |
bucle_interno: | bucle_interno: | ||
- | (... código ...) ; En este codigo no podemos usar D | + | (... código ...) |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 261: | Línea 261: | ||
\\ | \\ | ||
- | * Almacenaje de datos de entrada y salida en subrutinas: Podemos pasar parámetros a nuestras rutinas apilándolos | + | * Como veremos |
- | | + | \\ |
+ | | ||
- | | + | \\ |
+ | | ||
+ | |||
+ | <code z80> | ||
+ | push af | ||
+ | pop bc ; Ahora tenemos | ||
+ | </code> | ||
\\ | \\ | ||
+ | * El puntero de pila se puede utilizar también (como veremos más adelante en código más avanzado) para escribir ('' | ||
- | Recordad también que tenéis instrucciones | + | \\ |
+ | * Intercambiar valores | ||
<code z80> | <code z80> | ||
- | EX (SP), HL | + | ; Simulando "EX DE, BC" |
- | EX (SP), IX | + | |
- | EX (SP), IY | + | push bc ; Apilamos BC |
+ | push de ; Apilamos DE | ||
+ | pop bc ; Desapilamos BC | ||
+ | ; ahora BC=(valor apilado en push de) | ||
+ | pop de ; Desapilamos DE | ||
+ | ; ahora DE=(valor apilado en push bc) | ||
</ | </ | ||
+ | Hemos usado el código anterior para ilustrar la posibilidad de intercambiar valores de registros usando exclusivamente la pila, aunque el siguiente código (que también la usa) es bastante más eficiente ya que la pareja '' | ||
+ | |||
+ | <code z80> | ||
+ | ; Simulando "ex de, bc" | ||
+ | push hl | ||
+ | ld l, c | ||
+ | ld h, b | ||
+ | pop bc | ||
+ | </ | ||
+ | |||
+ | No sólo podemos intercambiar valores usando 2 '' | ||
+ | |||
+ | <code z80> | ||
+ | ex (sp), hl | ||
+ | ex (sp), ix | ||
+ | ex (sp), iy | ||
+ | </ | ||
+ | |||
+ | Con estas instrucciones podemos **simular** instrucciones que no existen en el Z80, como **EX BC, HL**, **EX BC, IX**, **EX BC, IY**, y otras combinaciones con diferentes registros. | ||
+ | |||
+ | Por ejemplo, supongamos que queremos intercambiar el valor del registro BC con el del registro IX, sin usar dos '' | ||
+ | |||
+ | <code z80> | ||
+ | push bc | ||
+ | ex (sp), ix ; IX = el valor de BC | ||
+ | pop bc ; BC = el valor de IX | ||
+ | </ | ||
+ | |||
+ | Las siguientes instrucciones podrían ser simuladas con este método: | ||
+ | |||
+ | |< 50% 50% 50% >| | ||
+ | ^ Instrucción incorrecta ^ Alternativa ^ | ||
+ | | ex bc, hl | push bc\\ ex (sp), hl\\ pop bc | | ||
+ | | ex bc, ix | push bc\\ ex (sp), ix\\ pop bc | | ||
+ | | ex bc, iy | push bc\\ ex (sp), iy\\ pop bc | | ||
+ | | ex af, hl | push af\\ ex (sp), hl\\ pop af | | ||
+ | | ex af, ix | push af\\ ex (sp), ix\\ pop af | | ||
+ | | ex af, iy | push af\\ ex (sp), iy\\ pop af | | ||
+ | | ex de, ix | push de\\ ex (sp), ix\\ pop de | | ||
+ | | ex de, iy | push de\\ ex (sp), iy\\ pop de | | ||
\\ | \\ | ||
Línea 288: | Línea 342: | ||
* Dado que la pila decrece en memoria, tenemos que tener cuidado con el valor de SP y la posición más alta de memoria donde hayamos almacenado datos o rutinas. Si ponemos un gráfico o una rutina cerca del valor inicial de SP, y realizamos muchas operaciones de PUSH, podemos sobreescribir nuestros datos con los valores que estamos apilando. | * Dado que la pila decrece en memoria, tenemos que tener cuidado con el valor de SP y la posición más alta de memoria donde hayamos almacenado datos o rutinas. Si ponemos un gráfico o una rutina cerca del valor inicial de SP, y realizamos muchas operaciones de PUSH, podemos sobreescribir nuestros datos con los valores que estamos apilando. | ||
- | * Hacer más '' | + | * Hacer más '' |
* Ampliando la regla anterior, hay que tener cuidado con los bucles a la hora de hacer '' | * Ampliando la regla anterior, hay que tener cuidado con los bucles a la hora de hacer '' | ||
- | * Finalmente, no hay que asumir que SP tiene un valor correcto para nosotros. Tal vez tenemos planeado usar una zona de la memoria para guardar datos o subrutinas y el uso de '' | + | * Finalmente, no hay que asumir que SP tiene un valor correcto para nosotros. Tal vez tenemos planeado usar una zona de la memoria para guardar datos o subrutinas y el uso de '' |
| | ||
Línea 299: | Línea 353: | ||
; Este programa se colgará (probablemente, | ; Este programa se colgará (probablemente, | ||
; pero en cualquier caso, no seguirá su ejecución normal. | ; pero en cualquier caso, no seguirá su ejecución normal. | ||
- | | + | |
- | | + | |
(código) | (código) | ||
- | | + | |
- | | + | |
; a la que teníamos que volver, volveremos a | ; a la que teníamos que volver, volveremos a | ||
; la dirección apuntada por el valor de BC, que | ; la dirección apuntada por el valor de BC, que | ||
Línea 315: | Línea 369: | ||
<code z80> | <code z80> | ||
bucle: | bucle: | ||
- | | + | |
(código que usa B) | (código que usa B) | ||
- | | + | |
- | | + | |
</ | </ | ||
Línea 327: | Línea 381: | ||
<code z80> | <code z80> | ||
bucle: | bucle: | ||
- | | + | |
(código) | (código) | ||
- | | + | |
- | | + | |
</ | </ | ||
Línea 337: | Línea 391: | ||
<code z80> | <code z80> | ||
- | | + | |
bucle: | bucle: | ||
(código) | (código) | ||
- | | + | |
- | | + | |
</ | </ | ||
Línea 363: | Línea 417: | ||
Otra opción segura es acotar en nuestro programa un espacio con DB / DS donde alojar la pila, en cualquier punto del mismo (al principio, al final, o en medio, no importa, siempre que no nos salgamos con PUSHes del espacio que le hemos dejado). En ese caso el problema es que ese " | Otra opción segura es acotar en nuestro programa un espacio con DB / DS donde alojar la pila, en cualquier punto del mismo (al principio, al final, o en medio, no importa, siempre que no nos salgamos con PUSHes del espacio que le hemos dejado). En ese caso el problema es que ese " | ||
- | La última opción es poner SP a 0, con lo que decrecerá desde el final de la RAM. Recuerda que cuando hacemos PUSH, primero se decrementa SP y luego se guardan los valores en memoria, por lo que SP = 0 usará $FFFE para el byte menos significativo y $FFFF para el más significativo (recordemos que el Z80 es Low-Endian). | + | La última opción es poner SP a 0, con lo que decrecerá desde el final de la RAM. Recuerda que cuando hacemos PUSH, primero se decrementa SP y luego se guardan los valores en memoria, por lo que SP = 0 usará $fffe para el byte menos significativo y $ffff para el más significativo (recordemos que el Z80 es Low-Endian). |
- | Pero si el target de nuestro programa es un modelo 128K y vamos a paginar, entonces el stack tiene que estar por debajo de $C000 ya que si no, al cambiar de banco lo perderíamos hasta volver al mismo (a menos que tengamos controlado que nuestro código no va a hacer ningún PUSH/POP hasta volver a poner el banco que tenía la pila, y además tengamos deshabilitadas las interrupciones). | + | Pero si el target de nuestro programa es un modelo 128K y vamos a paginar, entonces el stack tiene que estar por debajo de $c000 ya que si no, al cambiar de banco lo perderíamos hasta volver al mismo (a menos que tengamos controlado que nuestro código no va a hacer ningún PUSH/pop hasta volver a poner el banco que tenía la pila, y además tengamos deshabilitadas las interrupciones). |
Por lo tanto, mantenemos la recomendación que hicimos en los primeros capítulos del curso de dejar la pila por debajo de nuestro programa, con un '' | Por lo tanto, mantenemos la recomendación que hicimos en los primeros capítulos del curso de dejar la pila por debajo de nuestro programa, con un '' | ||
Línea 391: | Línea 445: | ||
Esto no tiene por qué ser un problema (de hecho, puede ser inapreciable) salvo que tengamos que hacer rutinas muy precisas o que estemos desarrollando un juego y necesitemos arañar hasta el último ciclo de reloj. Además, el problema no es que un PUSH/POP sea unos ciclos de reloj más lento en " | Esto no tiene por qué ser un problema (de hecho, puede ser inapreciable) salvo que tengamos que hacer rutinas muy precisas o que estemos desarrollando un juego y necesitemos arañar hasta el último ciclo de reloj. Además, el problema no es que un PUSH/POP sea unos ciclos de reloj más lento en " | ||
- | Otro punto que puede afectar a esto es si tenemos la pila en la contended memory y estamos haciendo un programa en C puro con Z88DK o en C con funciones en ensamblador, | + | Otro punto que puede afectar a esto es si tenemos la pila en la contended memory y estamos haciendo un programa en C puro con Z88DK o en C con funciones en ensamblador, |
No debemos obsesionarnos con el hecho de que la pila esté en la memoria en contienda, pero está bien saberlo y tenerlo en cuenta. | No debemos obsesionarnos con el hecho de que la pila esté en la memoria en contienda, pero está bien saberlo y tenerlo en cuenta. | ||
Línea 409: | Línea 463: | ||
El lector podría preguntar, ¿por qué no utilizar las instrucciones de salto '' | El lector podría preguntar, ¿por qué no utilizar las instrucciones de salto '' | ||
- | | + | |
<code z80> | <code z80> | ||
Línea 419: | Línea 473: | ||
SUMA_A_10: | SUMA_A_10: | ||
- | ADD A, 10 ; A = A + 10 | + | add a, 10 ; A = A + 10 |
- | LD B, A ; B = A | + | ld b, a ; B = A |
</ | </ | ||
Línea 428: | Línea 482: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
volver1: | volver1: | ||
Línea 438: | Línea 492: | ||
; Nota: Modifica el valor de A | ; Nota: Modifica el valor de A | ||
SUMA_A_10: | SUMA_A_10: | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
En este caso, cargaríamos A con el valor 35, saltaríamos a la subrutina, sumaríamos 10 a A (pasando a valer 45), haríamos B = 45, y volveríamos al lugar posterior al punto de llamada. | En este caso, cargaríamos A con el valor 35, saltaríamos a la subrutina, sumaríamos 10 a A (pasando a valer 45), haríamos B = 45, y volveríamos al lugar posterior al punto de llamada. | ||
- | Pero ... ¿qué pasaría si quisieramos volver a llamar a la subrutina desde otro punto de nuestro programa? Que sería inviable, porque nuestra subrutina acaba con un '' | + | Pero ... ¿qué pasaría si quisieramos volver a llamar a la subrutina desde otro punto de nuestro programa? Que sería inviable, porque nuestra subrutina acaba con un '' |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
volver1: | volver1: | ||
- | | + | |
- | | + | |
; Nunca llegariamos a volver aqui | ; Nunca llegariamos a volver aqui | ||
(...) | (...) | ||
SUMA_A_10: | SUMA_A_10: | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | Para evitar ese enorme problema es para lo que se usa **CALL** y **RET**. | + | Para evitar ese enorme problema es para lo que se usa **call** y **ret**. |
Línea 468: | Línea 522: | ||
===== Uso de CALL y RET ===== | ===== Uso de CALL y RET ===== | ||
- | '' | + | '' |
- | ¿Y para qué sirve eso? Para que lo aprovechemos dentro de nuestra subrutina con '' | + | ¿Y para qué sirve eso? Para que lo aprovechemos dentro de nuestra subrutina con '' |
Son, por tanto, el equivalente ensamblador de '' | Son, por tanto, el equivalente ensamblador de '' | ||
< | < | ||
- | CALL NN equivale a: | + | call NN equivale a: |
PUSH PC | PUSH PC | ||
- | | + | |
- | RET equivale a: | + | ret equivale a: |
POP PC | POP PC | ||
</ | </ | ||
Línea 486: | Línea 540: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
(...) | (...) | ||
SUMA_A_10: | SUMA_A_10: | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | En esta ocasión, cuando ejecutamos el primer '' | + | En esta ocasión, cuando ejecutamos el primer '' |
- | Al acabar la subrutina encontramos el '' | + | Al acabar la subrutina encontramos el '' |
Al hablar de la pila os contamos lo importante que era mantener la misma cantidad de PUSH que de POPs en nuestro código. Ahora entenderéis por qué: si dentro de una subrutina hacéis un '' | Al hablar de la pila os contamos lo importante que era mantener la misma cantidad de PUSH que de POPs en nuestro código. Ahora entenderéis por qué: si dentro de una subrutina hacéis un '' | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
SUMA_A_10: | SUMA_A_10: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; sino " | ; sino " | ||
</ | </ | ||
Línea 529: | Línea 583: | ||
| | ||
| | ||
- | CALL NN |- - - - - -| | + | call NN |- - - - - -| |
- | RET |- - - - - -| | + | ret |- - - - - -| |
</ | </ | ||
Línea 538: | Línea 592: | ||
La instrucción **RST** es un resquicio de la compatibilidad que el Z80 tiene con el procesador 8080. | La instrucción **RST** es un resquicio de la compatibilidad que el Z80 tiene con el procesador 8080. | ||
- | '' | + | '' |
Las siguientes instrucciones son equivalentes en cuanto a resultado de la ejecución: | Las siguientes instrucciones son equivalentes en cuanto a resultado de la ejecución: | ||
Línea 544: | Línea 598: | ||
|< 40% 50% 50% >| | |< 40% 50% 50% >| | ||
^ CALL ^ RST ^ | ^ CALL ^ RST ^ | ||
- | | CALL $0000 | RST $00 | | + | | call $0000 | rst $00 | |
- | | CALL $0008 | RST $08 | | + | | call $0008 | rst $08 | |
- | | CALL $0010 | RST $10 | | + | | call $0010 | rst $10 | |
- | | CALL $0018 | RST $18 | | + | | call $0018 | rst $18 | |
- | | CALL $0020 | RST $20 | | + | | call $0020 | rst $20 | |
- | | CALL $0028 | RST $28 | | + | | call $0028 | rst $28 | |
- | | CALL $0030 | RST $30 | | + | | call $0030 | rst $30 | |
- | | CALL $0038 | RST $38 | | + | | call $0038 | rst $38 | |
La principal ventaja de '' | La principal ventaja de '' | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | Por contra, | + | Por contra, |
La ventaja de tener estas instrucciones de salto de 1 sólo byte es que un programador puede colocar en estas direcciones rutinas que sean muy comunes de usar ($0008, $0010, etc), ahorrando 2 bytes en cada llamada que después se hagan a ellas. | La ventaja de tener estas instrucciones de salto de 1 sólo byte es que un programador puede colocar en estas direcciones rutinas que sean muy comunes de usar ($0008, $0010, etc), ahorrando 2 bytes en cada llamada que después se hagan a ellas. | ||
- | En el caso del Spectrum, estas direcciones de memoria caen en la ROM (no así en otros ordenadores que tienen la ROM al final, por ejemplo), por lo que no las podemos aprovechar en nuestros programas, aunque ya lo hicieron por nosotros los diseñadores de la ROM del Spectrum al colocar en esas direcciones de salto puntos de entrada a rutinas tan comunes como RST 16 (RST $10) que sirve, como ya hemos visto, para imprimir un carácter. | + | En el caso del Spectrum, estas direcciones de memoria caen en la ROM (no así en otros ordenadores que tienen la ROM al final, por ejemplo), por lo que no las podemos aprovechar en nuestros programas, aunque ya lo hicieron por nosotros los diseñadores de la ROM del Spectrum al colocar en esas direcciones de salto puntos de entrada a rutinas tan comunes como rst 16 (rst $10) que sirve, como ya hemos visto, para imprimir un carácter. |
\\ | \\ | ||
===== Saltos y retornos condicionales ===== | ===== Saltos y retornos condicionales ===== | ||
- | Una de las peculiaridades de CALL y RET es que tienen instrucciones condicionales con respecto al estado de los flags, igual que '' | + | Una de las peculiaridades de call y ret es que tienen instrucciones condicionales con respecto al estado de los flags, igual que '' |
Para eso, utilizamos las siguientes instrucciones: | Para eso, utilizamos las siguientes instrucciones: | ||
\\ | \\ | ||
- | * **CALL flag, NN** : Salta sólo si FLAG está activo. | + | * **call flag, NN** : Salta sólo si FLAG está activo. |
- | * **RET flag** : Vuelve sólo si FLAG está activo. | + | * **ret flag** : Vuelve sólo si FLAG está activo. |
\\ | \\ | ||
Línea 597: | Línea 651: | ||
; lo primero, comprobamos que BC no sea cero: | ; lo primero, comprobamos que BC no sea cero: | ||
- | | + | |
- | | + | |
; Si BC es cero, activará el flag Z | ; Si BC es cero, activará el flag Z | ||
- | | + | |
(más código) | (más código) | ||
- | ; Aquí seguiremos si BC no es cero, el RET no se habrá ejecutado. | + | ; Aquí seguiremos si BC no es cero, el ret no se habrá ejecutado. |
</ | </ | ||
Línea 614: | Línea 668: | ||
| | ||
| | ||
- | CALL cc, NN |- - - - - -| IF cc CALL NN | + | call cc, NN |- - - - - -| IF cc call NN |
- | RET cc |- - - - - -| IF cc RET | + | ret cc |- - - - - -| IF cc ret |
</ | </ | ||
Línea 635: | Línea 689: | ||
<code z80> | <code z80> | ||
; | ; | ||
- | ; MULTIPLI: Multiplica DE*BC | + | ; Mult_HL_DE: Multiplica DE*BC |
- | ; | + | ; |
- | ; | + | ; Entrada: |
- | ; | + | ; |
- | ; | + | ; Salida: |
+ | ; Modifica: | ||
; | ; | ||
- | MULTIPLICA: | + | Mult_HL_DE: |
- | | + | |
- | | + | |
- | | + | |
- | MULTI01: | + | multiloop_01: |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | Antes de hacer la llamada a MULTIPLICA, tendremos que cargar en DE y en BC los valores que queremos multiplicar, | + | Antes de hacer la llamada a '' |
| | ||
Línea 675: | Línea 730: | ||
<code z80> | <code z80> | ||
MiFuncion: | MiFuncion: | ||
- | | + | |
- | | + | |
(...) | (...) | ||
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 689: | Línea 744: | ||
Habrá casos en que no será necesario ponerlos. Si por ejemplo tenemos una función que ejecutamos al inicio del programa para, por ejemplo, precalcular algunos datos, no necesitaremos que preserve registros ya que sus valores al llamarlas no son importantes. También, en funciones muy críticas y que necesitan ser rápidas, en ocasiones no preservaremos los registros en ellas y lo que haremos será cerciorarnos en el código que hace la llamada que estas no modifican ningún registro que sea importante para nosotros en esa parte del código. | Habrá casos en que no será necesario ponerlos. Si por ejemplo tenemos una función que ejecutamos al inicio del programa para, por ejemplo, precalcular algunos datos, no necesitaremos que preserve registros ya que sus valores al llamarlas no son importantes. También, en funciones muy críticas y que necesitan ser rápidas, en ocasiones no preservaremos los registros en ellas y lo que haremos será cerciorarnos en el código que hace la llamada que estas no modifican ningún registro que sea importante para nosotros en esa parte del código. | ||
- | No nos olvidemos de que en algunos casos (muy pocos normalmente) podemos usar el juego de registros alternativos ('' | + | No nos olvidemos de que en algunos casos (muy pocos normalmente) podemos usar el juego de registros alternativos ('' |
\\ | \\ | ||
Línea 699: | Línea 754: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
(...) | (...) | ||
MiFuncion: | MiFuncion: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | (Codigo) | + | (... código ...) |
- | | + | |
- | | + | |
- | | + | |
x DB 0 | x DB 0 | ||
Línea 741: | Línea 796: | ||
En C (y en otros lenguajes de programación) los parámetros se insertan en la pila en el orden en que son leídos. La subrutina después lee los valores sin desapilarlos, | En C (y en otros lenguajes de programación) los parámetros se insertan en la pila en el orden en que son leídos. La subrutina después lee los valores sin desapilarlos, | ||
- | En ese caso, simplemente apilamos los parámetros con PUSH y dentro de la rutina los vamos recogiendo con POP: | + | En ese caso, simplemente apilamos los parámetros con '' |
| | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
(...) | (...) | ||
Rutina: | Rutina: | ||
- | | + | |
; (trabajamos con HL) | ; (trabajamos con HL) | ||
Línea 759: | Línea 814: | ||
; Ahora recogemos el parametro 2, | ; Ahora recogemos el parametro 2, | ||
; en HL o en cualquier otro registro | ; en HL o en cualquier otro registro | ||
- | | + | |
; (hacemos calculos con HL) | ; (hacemos calculos con HL) | ||
- | | + | |
- | | + | |
</ | </ | ||
Línea 773: | Línea 828: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
Rutina: | Rutina: | ||
- | | + | |
- | | + | |
; en la pila por el compilador (valor de Y) | ; en la pila por el compilador (valor de Y) | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; (ahora hacemos lo que queramos en asm) | ;;; (ahora hacemos lo que queramos en asm) | ||
- | | + | |
- | | + | |
</ | </ | ||
Línea 802: | Línea 857: | ||
<code z80> | <code z80> | ||
Rutina: | Rutina: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ; Aqui no necesitamos mas INC HL, no hay mas parametros | + | ; Aqui no necesitamos mas inc hl, no hay mas parametros |
; Nuestra rutina empieza a trabajar aquí | ; Nuestra rutina empieza a trabajar aquí | ||
Línea 839: | Línea 894: | ||
<code z80> | <code z80> | ||
BuclePrincipal: | BuclePrincipal: | ||
- | | + | |
- | | + | |
- | | + | |
- | jp Bucle_Principal | + | jp BuclePrincipal |
- | Leer_Teclado: | + | LeerTeclado: |
- | | + | |
- | Logica_Juego: | + | LogicaJuego: |
- | | + | |
- | Comprobar_Estado: | + | ComprobarEstado: |
- | | + | |
</ | </ | ||
Línea 864: | Línea 919: | ||
; Probamos de diferentes formas nuestra rutina | ; Probamos de diferentes formas nuestra rutina | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Rutina DrawSprite | ; Rutina DrawSprite | ||
Línea 874: | Línea 929: | ||
DrawSprite: | DrawSprite: | ||
(aquí el código) | (aquí el código) | ||
- | | + | |
sprite DB 0, | sprite DB 0, | ||
Línea 881: | Línea 936: | ||
</ | </ | ||
- | | + | |
Así, vamos creando diferentes rutinas en un entorno controlado y testeable, y las vamos incorporando a nuestro programa. Si hay algún bug en una rutina y tenemos que reproducirlo, | Así, vamos creando diferentes rutinas en un entorno controlado y testeable, y las vamos incorporando a nuestro programa. Si hay algún bug en una rutina y tenemos que reproducirlo, | ||
Línea 889: | Línea 944: | ||
En ocasiones una excesiva desgranación del programa en módulos más pequeños puede dar lugar a una penalización en el rendimiento, | En ocasiones una excesiva desgranación del programa en módulos más pequeños puede dar lugar a una penalización en el rendimiento, | ||
- | Hay gente que, en lugar de esto, preferirá realizar una función específica que dibuje los 10x10 bloques dentro de una misma función. Esto es así porque de este modo te evitas 100 CALLs (10x10) y sus correspondientes RETs, lo cual puede ser importante en una rutina gráfica que se ejecute X veces por segundo. Por supuesto, en muchos casos tendrán razón, en ciertas ocasiones hay que hacer rutinas concretas para tareas concretas, aún cuando puedan repetir parte de otro código que hayamos escrito anteriormente, | + | Hay gente que, en lugar de esto, preferirá realizar una función específica que dibuje los 10x10 bloques dentro de una misma función. Esto es así porque de este modo te evitas 100 calls (10x10) y sus correspondientes RETs, lo cual puede ser importante en una rutina gráfica que se ejecute X veces por segundo. Por supuesto, en muchos casos tendrán razón, en ciertas ocasiones hay que hacer rutinas concretas para tareas concretas, aún cuando puedan repetir parte de otro código que hayamos escrito anteriormente, |
Pero si, por ejemplo, nosotros sólo dibujamos la pantalla una vez cuando nuestro personaje sale por el borde, y no volvemos a dibujar otra hasta que sale por otro borde (típico caso de juegos sin scroll que muestran pantallas completas de una sóla vez), vale la pena el usar funciones modulares dado que unos milisegundos más de ejecución en el trazado de la pantalla no afectarán al desarrollo del juego. | Pero si, por ejemplo, nosotros sólo dibujamos la pantalla una vez cuando nuestro personaje sale por el borde, y no volvemos a dibujar otra hasta que sale por otro borde (típico caso de juegos sin scroll que muestran pantallas completas de una sóla vez), vale la pena el usar funciones modulares dado que unos milisegundos más de ejecución en el trazado de la pantalla no afectarán al desarrollo del juego. | ||
Línea 927: | Línea 982: | ||
* {{cursos: | * {{cursos: | ||
* {{cursos: | * {{cursos: | ||
- | * {{cursos: | + | * {{cursos: |
* {{cursos: | * {{cursos: | ||
* {{cursos: | * {{cursos: |