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Última revisiónAmbos lados, revisión siguiente | ||
cursos:ensamblador:asmz88dk [10-01-2024 08:44] – [Funciones escritas ASM llamables desde C] sromero | cursos:ensamblador:asmz88dk [19-01-2024 12:32] – sromero | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
====== Integración de ASM en Z88DK ====== | ====== Integración de ASM en Z88DK ====== | ||
- | En este capítulo vamos a ver cómo podemos usar ASM dentro del compilador de C más utilizado en Spectrum: el Z88DK. | + | En este capítulo vamos a ver cómo podemos usar ASM dentro del compilador de C más utilizado en Spectrum: el **Z88DK** (//Z88 Development Kit//). |
La idea de hacer programas mixtos en C con pequeñas partes en ASM es la de escribir en ensamblador las partes más críticas o importantes del mismo para acelerar su ejecución. | La idea de hacer programas mixtos en C con pequeñas partes en ASM es la de escribir en ensamblador las partes más críticas o importantes del mismo para acelerar su ejecución. | ||
Por ejemplo, en un juego podríamos escribir el esqueleto del programa en C, incluyendo menú del juego, presentación, | Por ejemplo, en un juego podríamos escribir el esqueleto del programa en C, incluyendo menú del juego, presentación, | ||
+ | |||
+ | Es importante destacar que cuando usamos ASM con Z88DK, éste se ensambla con **z80asm**, el ensamblador incluído en Z88DK, y no lo haremos con pasmo o sjasmplus. Z80ASM tiene sus propias peculiaridades (formato de las etiquetas, macros, directivas de ensamblador, | ||
+ | |||
+ | * https:// | ||
+ | |||
\\ | \\ | ||
===== Embeber ASM dentro de código C ===== | ===== Embeber ASM dentro de código C ===== | ||
- | Z88DK permite utilizar ASM de diferentes formas. En nuestro caso vamos a ver cómo se "embebe" | + | Z88DK permite utilizar ASM de diferentes formas. En nuestro caso vamos a ver cómo se embebe C en ASM con las directivas |
<code c> | <code c> | ||
Línea 34: | Línea 39: | ||
{ | { | ||
#asm | #asm | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
#endasm | #endasm | ||
} | } | ||
</ | </ | ||
- | No es necesario hacer PUSH y POP de los registros para preservar sus valores porque Z88DK lo hace automáticamente por nosotros. | + | No es necesario hacer '' |
| | ||
Línea 52: | Línea 57: | ||
</ | </ | ||
- | Este ejemplo es muy sencillo y no ha necesitado parámetros de entrada, pero en muchas ocasiones necesitaremos poder pasar parámetros en las llamadas de las funciones, y recibir valores desde las mismas. Veamos cómo se haría. | + | Este ejemplo es muy sencillo y no ha necesitado parámetros de entrada, pero en muchas ocasiones necesitaremos poder pasar parámetros en las llamadas de las funciones, y recibir valores desde las mismas. |
\\ | \\ | ||
- | ===== Cómo leer parámetros de funciones | + | ===== Cómo leer parámetros de llamada |
En C (y en otros lenguajes de programación) los parámetros se insertan normalmente en la pila en el orden en que aparecen en el código del programa. La subrutina debe utilizar el registro SP (sin modificarlo, | En C (y en otros lenguajes de programación) los parámetros se insertan normalmente en la pila en el orden en que aparecen en el código del programa. La subrutina debe utilizar el registro SP (sin modificarlo, | ||
Línea 61: | Línea 66: | ||
| | ||
- | < | + | < |
// | // | ||
// Sea parte de nuestro programa en C: | // Sea parte de nuestro programa en C: | ||
Línea 78: | Línea 83: | ||
#asm | #asm | ||
- | | + | |
- | | + | |
; 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 98: | Línea 103: | ||
</ | </ | ||
- | No tenemos que preocuparnos por hacer PUSH y POP de los registros para preservar su valor dado que Z88DK lo hace automáticamente antes y después de cada #asm y #endasm. | + | Como hemos comentado en un apartado anterior, no tenemos que preocuparnos por hacer '' |
- | El problema es que conforme crece el número de parámetros apilados, es posible que tengamos que hacer malabarismos para almacenarlos, | + | El problema es que conforme crece el número de parámetros apilados, es posible que tengamos que hacer malabarismos para almacenarlos, |
- | < | + | < |
// | // | ||
int Funcion(int x, int y, int z) | int Funcion(int x, int y, int z) | ||
Línea 108: | Línea 113: | ||
#asm | #asm | ||
- | | + | |
- | | + | |
; en la pila por el compilador (z) | ; en la pila por el compilador (z) | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; (ahora hacemos lo que queramos en asm) | ;;; (ahora hacemos lo que queramos en asm) | ||
Línea 139: | Línea 144: | ||
La manera de leer bytes (variables de tipo char) pulsados en C es de la misma forma que leemos una palabra de 16 bits, pero ignorando la parte alta. En realidad, como la pila es de 16 bits, el compilador convierte el dato de 8 bits en uno de 16 (rellenando con ceros) y mete en la pila este valor: | La manera de leer bytes (variables de tipo char) pulsados en C es de la misma forma que leemos una palabra de 16 bits, pero ignorando la parte alta. En realidad, como la pila es de 16 bits, el compilador convierte el dato de 8 bits en uno de 16 (rellenando con ceros) y mete en la pila este valor: | ||
- | < | + | < |
// | // | ||
int Funcion(char x, char y) | int Funcion(char x, char y) | ||
Línea 145: | Línea 150: | ||
#asm | #asm | ||
- | | + | |
- | | + | |
- | ; en la pila por el compilador (z) | + | ; en la pila por el compilador (y) |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | + | ||
- | LD A, (HL) ; Aquí tenemos nuestro dato de 8 bits (x) | + | |
- | LD C, A | + | |
- | INC HL | + | |
- | INC HL ; La parte alta del byte no nos interesa | + | |
+ | ld a, (hl) ; Aquí tenemos nuestro dato de 8 bits (x) | ||
+ | ld c, a | ||
+ | ; Si hubiera más parámetros, | ||
+ | ; "inc hl" para acceder a ellos. Como es el último, no es necesario. | ||
+ | | ||
;;; (ahora hacemos lo que queramos en asm) | ;;; (ahora hacemos lo que queramos en asm) | ||
#endasm | #endasm | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Por ejemplo, veamos nuestra rutina anterior de '' | ||
+ | |||
+ | <code c> | ||
+ | void ClearScreenValue(char value) | ||
+ | { | ||
+ | #asm | ||
+ | ld hl, 2 | ||
+ | add hl, sp ; Ahora SP apunta al ultimo parametro metido | ||
+ | ; en la pila por el compilador (value) | ||
+ | ld a, (hl) ; Aquí tenemos nuestro dato de 8 bits (value) | ||
+ | | ||
+ | ld hl, 16384 ; HL = Inicio de la videoram | ||
+ | ld (hl), a ; Escribimos el patron A en (HL) | ||
+ | ld de, 16385 ; Apuntamos DE a 16385 | ||
+ | ld bc, 192*32-1 | ||
+ | ldir | ||
+ | #endasm | ||
} | } | ||
</ | </ | ||
Línea 166: | Línea 191: | ||
En ocasiones, es posible que incluso tengamos que utilizar variables auxiliares de memoria para guardar datos: | En ocasiones, es posible que incluso tengamos que utilizar variables auxiliares de memoria para guardar datos: | ||
- | < | + | < |
// | // | ||
int Funcion(int x, int y, char z) | int Funcion(int x, int y, char z) | ||
Línea 172: | Línea 197: | ||
#asm | #asm | ||
- | | + | |
- | | + | |
; en la pila por el compilador (z) | ; en la pila por el compilador (z) | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
;;; (ahora hacemos lo que queramos en asm) | ;;; (ahora hacemos lo que queramos en asm) | ||
- | | + | |
valor_x | valor_x | ||
Línea 209: | Línea 234: | ||
===== Devolver parámetros desde funciones escritas en ASM ===== | ===== Devolver parámetros desde funciones escritas en ASM ===== | ||
- | Por contra, para devolver valores no se utiliza la pila dado que no podemos tocarla, ya que el RET volvería | + | Por contra, para devolver valores no se utiliza la pila dado que no podemos tocarla, ya que el '' |
<code c> | <code c> | ||
Línea 218: | Línea 243: | ||
\\ | \\ | ||
===== Ejemplos de Integracion de ASM en Z88DK ===== | ===== Ejemplos de Integracion de ASM en Z88DK ===== | ||
+ | |||
+ | \\ | ||
+ | ==== Ejemplos varios ==== | ||
Para aprovechar esta introducción de "uso de ASM en Z88DK", | Para aprovechar esta introducción de "uso de ASM en Z88DK", | ||
- | < | + | Como ejemplo |
- | // | + | |
- | // Devuelve la direccion | + | <code c> |
- | // de pantalla, de coordenadas (x,y). Usando la dirección | + | |
- | // devuelve | + | |
- | // leer o cambiar los atributos de dicho carácter. | + | |
- | // | + | |
- | // Llamada: | + | |
- | // | + | |
- | int Get_LOWRES_Attrib_Address(char x, char y) | + | |
- | { | + | |
- | #asm | + | |
- | + | ||
- | LD HL, 2 | + | |
- | ADD HL, SP ; Leemos x e y de la pila | + | |
- | LD D, (HL) ; d = y | + | |
- | INC HL ; Primero | + | |
- | INC HL ; Como son "char", | + | |
- | LD E, (HL) ; e = x | + | |
- | + | ||
- | LD H, 0 | + | |
- | LD L, D | + | |
- | ADD HL, HL ; HL = HL*2 | + | |
- | ADD HL, HL ; HL = HL*4 | + | |
- | ADD HL, HL ; HL = HL*8 | + | |
- | ADD HL, HL ; HL = HL*16 | + | |
- | ADD HL, HL ; HL = HL*32 | + | |
- | LD D, 0 | + | |
- | ADD HL, DE ; Ahora HL = (32*y)+x | + | |
- | LD BC, 16384+6144 | + | |
- | ADD HL, BC ; Sumamos y devolvemos en HL | + | |
- | + | ||
- | #endasm | + | |
- | } | + | |
- | + | ||
- | // | + | |
- | // Set Border | + | |
- | // Ejemplo | + | |
- | // globales | + | |
- | // | + | |
- | unsigned char bordeactual; | + | |
- | + | ||
- | void BORDER( unsigned char value ) | + | |
- | { | + | |
- | #asm | + | |
- | LD HL, 2 | + | |
- | ADD HL, SP | + | |
- | LD A, (HL) | + | |
- | LD C, 254 | + | |
- | OUT (C), A | + | |
- | LD (_bordeactual), | + | |
- | + | ||
- | RLCA ; Adaptamos el borde para guardarlo | + | |
- | RLCA ; en la variable | + | |
- | RLCA ; Color borde -> a zona de PAPER | + | |
- | LD HL, 23624 ; lo almacenamos | + | |
- | LD (HL), A ; lo usen las rutinas de la ROM. | + | |
- | #endasm | + | |
- | } | + | |
- | + | ||
// | // | ||
// Realización de un fundido de la pantalla hacia negro | // Realización de un fundido de la pantalla hacia negro | ||
Línea 288: | Línea 258: | ||
// ellos y con un punto " | // ellos y con un punto " | ||
// | // | ||
- | void FadeScreen( void ) | + | void FadeScreen(void) |
{ | { | ||
#asm | #asm | ||
- | | + | |
.fadescreen_loop1 | .fadescreen_loop1 | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
.fadescreen_loop2 | .fadescreen_loop2 | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
.fadescreen_ink_zero | .fadescreen_ink_zero | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
.fadescreen_paper_zero | .fadescreen_paper_zero | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
#endasm | #endasm | ||
Línea 347: | Línea 317: | ||
</ | </ | ||
- | Un detalle a tener en cuenta, | + | Un detalle a tener en cuenta, |
\\ | \\ | ||
Línea 354: | Línea 324: | ||
En la anterior captura podéis ver el aspecto de uno de los pasos del fundido. | En la anterior captura podéis ver el aspecto de uno de los pasos del fundido. | ||
+ | |||
+ | A continuación vamos a ver una rutina que calcula la dirección de un atributo de pantalla dadas las coordenadas X,Y en baja resolución (0-31, 0-23) de un " | ||
+ | |||
+ | El cálculo resultante se almacena en HL para que este valor sea devuelto a C, de forma que se asigne como resultado de la llamada. | ||
+ | |||
+ | <code c> | ||
+ | // | ||
+ | // Devuelve la direccion de memoria del atributo de un caracter | ||
+ | // de pantalla, de coordenadas (x,y). Usando la dirección que | ||
+ | // devuelve esta función (en HL, devuelto en la llamada), podemos | ||
+ | // leer o cambiar los atributos de dicho carácter. | ||
+ | // | ||
+ | // Llamada: | ||
+ | // | ||
+ | int Get_LOWRES_Attrib_Address(char x, char y) | ||
+ | { | ||
+ | #asm | ||
+ | |||
+ | ld hl, 2 | ||
+ | add hl, sp ; Leemos x e y de la pila | ||
+ | ld d, (hl) ; d = y | ||
+ | inc hl ; Primero " | ||
+ | inc hl ; Como son " | ||
+ | ld e, (hl) ; e = x | ||
+ | |||
+ | ld h, 0 | ||
+ | ld l, d | ||
+ | add hl, hl ; HL = HL*2 | ||
+ | add hl, hl ; HL = HL*4 | ||
+ | add hl, hl ; HL = HL*8 | ||
+ | add hl, hl ; HL = HL*16 | ||
+ | add hl, hl ; HL = HL*32 | ||
+ | ld d, 0 | ||
+ | add hl, de ; Ahora HL = (32*y)+x | ||
+ | ld bc, 16384+6144 | ||
+ | add hl, bc ; Sumamos y devolvemos en HL | ||
+ | #endasm | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | El siguiente ejemplo muestra cómo acceder a variables globales del programa en C desde nuestro código en ASM, utilizando un " | ||
+ | |||
+ | <code c> | ||
+ | // | ||
+ | // Set Border | ||
+ | // Ejemplo de modificación del borde, muestra cómo leer variables | ||
+ | // globales de C en ASM, añadiendo " | ||
+ | // | ||
+ | unsigned char bordeactual; | ||
+ | |||
+ | void BORDER(unsigned char value) | ||
+ | { | ||
+ | #asm | ||
+ | ld hl, 2 | ||
+ | add hl, sp | ||
+ | ld a, (hl) ; A = parametro de la pila (value) | ||
+ | | ||
+ | ld c, $FE | ||
+ | out (c), a | ||
+ | ld (_bordeactual), | ||
+ | #endasm | ||
+ | } | ||
+ | </ | ||
+ | |||
\\ | \\ | ||
+ | ==== Rutina descompresora de RLE en C+ASM ==== | ||
+ | |||
+ | A continuación se muestra la función de descompresión RLE escrita en ASM embebido en una función de C. El ejemplo demuestra de nuevo la lectura de parámetros (en este caso, direcciones de memoria origen, destino, y un tamaño en 16 bits), así como las etiquetas que usa Z88DK, precedidas por punto. | ||
+ | |||
+ | <code c> | ||
+ | int RLE_decompress_ASM(unsigned char *, unsigned char *, int); | ||
+ | |||
+ | // | ||
+ | // RLE_decompress_ASM( src, dst, longitud ); | ||
+ | // | ||
+ | int RLE_decompress_ASM( unsigned char *src, unsigned char *dst, int length ) | ||
+ | { | ||
+ | |||
+ | #asm | ||
+ | ld hl,2 | ||
+ | add hl,sp | ||
+ | |||
+ | ld c, (hl) | ||
+ | inc hl | ||
+ | ld b, (hl) | ||
+ | inc hl // BC = lenght | ||
+ | |||
+ | ld e, (hl) | ||
+ | inc hl | ||
+ | ld d, (hl) | ||
+ | inc hl // de = dst | ||
+ | push de | ||
+ | |||
+ | ld e, (hl) | ||
+ | inc hl | ||
+ | ld d, (hl) | ||
+ | inc hl // de = src | ||
+ | |||
+ | ex de, hl | ||
+ | pop de // now de = dst and hl = src | ||
+ | |||
+ | // After this: HL = source, DE = destination, | ||
+ | |||
+ | .RLE_dec_loop | ||
+ | ld a, | ||
+ | |||
+ | cp 192 | ||
+ | jp nc, RLE_dec_compressed | ||
+ | ld (de), a // si no está comprimido, escribirlo | ||
+ | inc de | ||
+ | inc hl | ||
+ | dec bc | ||
+ | |||
+ | .RLE_dec_loop2 | ||
+ | ld a,b | ||
+ | or c | ||
+ | jr nz, RLE_dec_loop | ||
+ | ret // miramos si hemos acabado | ||
+ | |||
+ | .RLE_dec_compressed | ||
+ | push bc | ||
+ | and 63 // cogemos el numero de repeticiones | ||
+ | ld b, a // lo salvamos en B | ||
+ | inc hl // y leemos otro byte (dato a repetir) | ||
+ | ld a, (hl) | ||
+ | |||
+ | .RLE_dec_loop3 | ||
+ | ld (de), | ||
+ | inc de | ||
+ | djnz RLE_dec_loop3 | ||
+ | inc hl | ||
+ | pop bc // recuperamos BC | ||
+ | dec bc // Este dec bc puede hacer BC=0 si los datos | ||
+ | // RLE no correctos. Cuidado (mem-smashing). | ||
+ | dec bc | ||
+ | jr RLE_dec_loop2 | ||
+ | ret | ||
+ | |||
+ | #endasm | ||
+ | } | ||
+ | </ | ||
+ | |||
===== Paginación de memoria desde Z88DK (C) ===== | ===== Paginación de memoria desde Z88DK (C) ===== | ||
Línea 363: | Línea 474: | ||
//--- SetRAMBank ------------------------------------------------------ | //--- SetRAMBank ------------------------------------------------------ | ||
// | // | ||
- | // Se mapea el banco (0-7) indicado sobre $C000. | + | // Se mapea el banco (0-7) indicado sobre $c000. |
// | // | ||
// Ojo: en esta función no se deshabilitan las interrupciones y además, | // Ojo: en esta función no se deshabilitan las interrupciones y además, | ||
Línea 377: | Línea 488: | ||
ld b, a | ld b, a | ||
- | ld a, ($5B5C) | + | ld a, ($5b5c) |
and f8h | and f8h | ||
or b | or b | ||
- | ld bc, $7FFD | + | ld bc, $7ffd |
- | ld ($5B5C), a | + | ld ($5b5c), a |
out (c), a ; Realizamos cambio de banco | out (c), a ; Realizamos cambio de banco | ||
# | # | ||
Línea 387: | Línea 498: | ||
</ | </ | ||
- | Con el anterior código podemos mapear uno de los bancos de memoria de 16KB sobre la página que va desde $C000 a $FFFF, pero debido al uso de memoria, variables y estructuras internas que hace Z88DK, debemos seguir una serie de consideraciones. | + | Con el anterior código podemos mapear uno de los bancos de memoria de 16KB sobre la página que va desde $c000 a $ffff, pero debido al uso de memoria, variables y estructuras internas que hace Z88DK, debemos seguir una serie de consideraciones. |
+ | |||
+ | * Todo el código en ejecución debe estar por debajo de $c000, para lo cual es recomendable definir los gráficos al final del " | ||
- | * Todo el código en ejecución debe estar por debajo de $C000, para lo cual es recomendable definir los gráficos al final del " | ||
* Es importantísimo colocar la pila en la memoria baja, mediante la siguiente instrucción (o similar, según la dirección en que queremos colocarla) al principio de nuestro programa: | * Es importantísimo colocar la pila en la memoria baja, mediante la siguiente instrucción (o similar, según la dirección en que queremos colocarla) al principio de nuestro programa: | ||
\\ | \\ | ||
- | < | + | < |
/* Allocate space for the stack */ | /* Allocate space for the stack */ | ||
#pragma output STACKPTR=24500 | #pragma output STACKPTR=24500 | ||
Línea 399: | Línea 511: | ||
\\ | \\ | ||
- | La regla general es asegurarse de que no haya nada importante (para la ejecución de nuestro programa) en el bloque $C000 a $FFFF cuando se haga el cambio: ni la pila, ni código al que debamos acceder. Tan sólo datos que puedan ser intercambiandos de un banco a otro sin riesgo para la ejecución del mismo (por ejemplo, los datos de un nivel de juego en el que ya no estamos). | + | La regla general es asegurarse de que no haya nada importante (para la ejecución de nuestro programa) en el bloque $c000 a $ffff cuando se haga el cambio: ni la pila, ni código al que debamos acceder. Tan sólo datos que puedan ser intercambiandos de un banco a otro sin riesgo para la ejecución del mismo (por ejemplo, los datos de un nivel de juego en el que ya no estamos). |
+ | |||
+ | |||
+ | \\ | ||
+ | ===== Enlaces ===== | ||
+ | |||
+ | * [[https:// | ||
\\ | \\ | ||
**[ [[.: | **[ [[.: |