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 Próxima revisiónAmbos lados, revisión siguiente | ||
cursos:ensamblador:paginacion_128k [08-01-2024 05:26] – sromero | cursos:ensamblador:paginacion_128k [19-01-2024 08:14] – sromero | ||
---|---|---|---|
Línea 10: | Línea 10: | ||
Los modelos de Spectrum 128K, +2, +2A, +2B y +3 disponen de 128KB de memoria, aunque no toda está disponible simultáneamente. Al igual que en el modelo de 48KB, el microprocesador Z80 sólo puede direccionar 64K-direcciones de memoria, por lo que para acceder a esta memoria " | Los modelos de Spectrum 128K, +2, +2A, +2B y +3 disponen de 128KB de memoria, aunque no toda está disponible simultáneamente. Al igual que en el modelo de 48KB, el microprocesador Z80 sólo puede direccionar 64K-direcciones de memoria, por lo que para acceder a esta memoria " | ||
- | | + | |
Así que ... ¿cómo hacemos para utilizar más de 64KB de memoria si nuestro procesador sólo puede leer datos de la celdilla 0, 1, 2, 3 ... 65534 y 65535? La respuesta es: **mediante la paginación**. | Así que ... ¿cómo hacemos para utilizar más de 64KB de memoria si nuestro procesador sólo puede leer datos de la celdilla 0, 1, 2, 3 ... 65534 y 65535? La respuesta es: **mediante la paginación**. | ||
- | Los 64KB de memoria del Spectrum, se dividen en 4 bloques de 16KB. El primer bloque ($0000 - $4000) está mapeado sobre la ROM del Spectrum. Accediendo a los bytes desde $0000 a $3FFF de la memoria estamos accediendo a los bytes $0000 a $3FFF del "chip (físico)" | + | Los 64KB de memoria del Spectrum, se dividen en 4 bloques de 16KB. El primer bloque ($0000 - $4000) está mapeado sobre la ROM del Spectrum. Accediendo a los bytes desde $0000 a $3fff de la memoria estamos accediendo a los bytes $0000 a $3fff del "chip (físico)" |
- | | + | |
- | El bloque que nos interesa a nosotros es el cuarto. La zona final del área de memoria, desde $C000 a $FFFF, es nuestra " | + | El bloque que nos interesa a nosotros es el cuarto. La zona final del área de memoria, desde $c000 a $ffff, es nuestra " |
| | ||
Línea 28: | Línea 28: | ||
| | ||
- | La última porción de 16KB de la memoria es, pues, una " | + | La última porción de 16KB de la memoria es, pues, una " |
- | Si paginamos el bloque 1 en nuestra ventana $C000-$FFFF, cuando accedamos a este rango de memoria, ya no accederíamos a los mismos datos que guardamos en el banco 0, sino a la zona de memoria "Banco 1". Es posible incluso mapear la zona de pantalla (Banco 5), de forma que las direcciones $4000 y $C000 serían equivalentes: | + | Si paginamos el bloque 1 en nuestra ventana $c000-$ffff, cuando accedamos a este rango de memoria, ya no accederíamos a los mismos datos que guardamos en el banco 0, sino a la zona de memoria "Banco 1". Es posible incluso mapear la zona de pantalla (Banco 5), de forma que las direcciones $4000 y $c000 serían equivalentes: |
- | El mapa de memoria del Spectrum con los bloques mapeables/ | + | El mapa de memoria del Spectrum con los bloques mapeables/ |
\\ | \\ | ||
Línea 41: | Línea 41: | ||
===== Cambiando de banco ===== | ===== Cambiando de banco ===== | ||
- | El puerto que controla la paginación en los modelos 128K es el $7FFD. En realidad, nuestro Spectrum sólo decodifica los bits 1 y 15, por lo que cualquier combinación con los bits 1 y 15 a cero accederá a la gestión de paginación. No obstante, se recomiente utilizar $7FFD por compatibilidad con otros sistemas. | + | El puerto que controla la paginación en los modelos 128K es el $7ffd. En realidad, nuestro Spectrum sólo decodifica los bits 1 y 15, por lo que cualquier combinación con los bits 1 y 15 a cero accederá a la gestión de paginación. No obstante, se recomiente utilizar $7ffd por compatibilidad con otros sistemas. |
- | La lectura del puerto $7FFD no devolverá ningún valor útil, pero sí podemos escribir en él un valor con cierto formato: | + | La lectura del puerto $7ffd no devolverá ningún valor útil, pero sí podemos escribir en él un valor con cierto formato: |
|< 60% >| | |< 60% >| | ||
Línea 50: | Línea 50: | ||
| 3 | Visualizar la pantalla gráfica " | | 3 | Visualizar la pantalla gráfica " | ||
| 4 | Selección de la ROM, entre (0) ROM BASIC 128K (Menú y editor), y (1) BASIC 48K. | | | 4 | Selección de la ROM, entre (0) ROM BASIC 128K (Menú y editor), y (1) BASIC 48K. | | ||
- | | 5 | Si lo activamos, se desactivará el paginado de memoria hasta que se resetee el\\ Spectrum. El hardware ignorará toda escritura al puerto $7FFD. | | + | | 5 | Si lo activamos, se desactivará el paginado de memoria hasta que se resetee el\\ Spectrum. El hardware ignorará toda escritura al puerto $7ffd. | |
A la hora de cambiar de página de memoria hay que tener en cuenta lo siguiente: | A la hora de cambiar de página de memoria hay que tener en cuenta lo siguiente: | ||
\\ | \\ | ||
- | * La pila (stack) debe de estar ubicada en una zona de memoria que no vaya a ser paginada (no puede estar dentro de la zona que va a cambiar). | ||
- | |||
* Las interrupciones deben de estar deshabilitadas para realizar el cambio de banco. | * Las interrupciones deben de estar deshabilitadas para realizar el cambio de banco. | ||
- | * Si se va a ejecutar código con interrupciones (y no pueden estar deshabilitadas), | + | |
+ | |||
+ | * Si estamos utilizando o vamos a utilizar el modo de interrupciones 2 ('' | ||
+ | |||
+ | | ||
\\ | \\ | ||
Línea 65: | Línea 67: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 84: | Línea 86: | ||
; | ; | ||
SetRAMBank: | SetRAMBank: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
- | + | Un detalle apuntado por la documentación de World Of Spectrum es que los bancos 1, 3, 5 y 7 son " | |
- | Un detalle apuntado por la documentación de World Of Spectrum es que los bancos 1, 3, 5 y 7 son " | + | |
\\ | \\ | ||
Línea 106: | Línea 107: | ||
* Los bancos de contended-memory son los bloques 4, 5, 6 y 7 (no el 1, 3, 5 y 7). | * Los bancos de contended-memory son los bloques 4, 5, 6 y 7 (no el 1, 3, 5 y 7). | ||
- | * +2A y +3 tienen 4 ROMS en lugar de 2, por lo que el bit 4 del puerto $7FFD se convierte ahora en el bit bajo de la ROM a seleccionar, | + | * +2A y +3 tienen 4 ROMS en lugar de 2, por lo que el bit 4 del puerto $7ffd se convierte ahora en el bit bajo de la ROM a seleccionar, |
- | * +2A y +3 tienen funcionalidades extra de paginado, que se controlan con el puerto $1FFD. | + | * +2A y +3 tienen funcionalidades extra de paginado, que se controlan con el puerto $1ffd. |
\\ | \\ | ||
- | Este puerto (el $1FFD) tiene el siguiente significado a la hora de escribir en él: | + | Este puerto (el $1ffd) tiene el siguiente significado a la hora de escribir en él: |
|< 60% >| | |< 60% >| | ||
Línea 120: | Línea 121: | ||
| 3-4 | 3=Motor del disco (1/0, ON/OFF), 4=Impresora | | | 3-4 | 3=Motor del disco (1/0, ON/OFF), 4=Impresora | | ||
- | | + | |
\\ | \\ | ||
Línea 135: | Línea 136: | ||
| 3 | BASIC 48K | | | 3 | BASIC 48K | | ||
- | De nuevo, al igual que en el caso del puerto genérico sobre paginación, | + | De nuevo, al igual que en el caso del puerto genérico sobre paginación, |
\\ | \\ | ||
Línea 143: | Línea 144: | ||
\\ | \\ | ||
- | * Paginamos el bloque/ | + | * Paginamos el bloque/ |
- | * Escribimos en memoria, en la posición $C000, el valor $AA. | + | * Escribimos en memoria, en la posición $c000, el valor $aa. |
- | * Paginamos el bloque/ | + | * Paginamos el bloque/ |
- | * Escribimos en memoria, en la posición $C000, el valor $01. | + | * Escribimos en memoria, en la posición $c000, el valor $01. |
* Volvemos a paginar el banco 0 sobre el área de paginación. | * Volvemos a paginar el banco 0 sobre el área de paginación. | ||
- | * Leemos el valor de la posición de memoria $C000 y rellenamos toda la pantalla con dicho valor. | + | * Leemos el valor de la posición de memoria $c000 y rellenamos toda la pantalla con dicho valor. |
* Volvemos a paginar el banco 1 sobre el área de paginación. | * Volvemos a paginar el banco 1 sobre el área de paginación. | ||
- | * Leemos el valor de la posición de memoria $C000 y rellenamos toda la pantalla con dicho valor. | + | * Leemos el valor de la posición de memoria $c000 y rellenamos toda la pantalla con dicho valor. |
\\ | \\ | ||
- | | + | |
Para terminar de comprender el ejemplo, lo mejor es compilarlo y ejecutarlo: | Para terminar de comprender el ejemplo, lo mejor es compilarlo y ejecutarlo: | ||
Línea 169: | Línea 170: | ||
; Demostracion del uso de bancos / paginación en modo 128K | ; Demostracion del uso de bancos / paginación en modo 128K | ||
; | ; | ||
+ | ORG 35000 | ||
- | ORG 35000 | + | call CLS |
- | | + | |
+ | add hl, sp ; Guardamos el valor actual de SP | ||
+ | ex de, hl ; lo almacenamos en DE | ||
- | | + | |
- | ADD HL, SP | + | |
- | EX DE, HL ; lo almacenamos en DE | + | |
- | | + | |
+ | ld hl, $c000 ; | ||
- | | + | |
- | | + | |
+ | ld b, 0 | ||
+ | call SetRAMBank | ||
- | | + | |
- | | + | |
- | LD B, 0 | + | |
- | CALL SetRAMBank | + | |
- | | + | |
- | | + | |
+ | ld b, 1 | ||
+ | call SetRAMBank | ||
- | | + | |
- | ; en el primer byte de sus 16K (en la direccion 0xc000): | + | |
- | LD B, 1 | + | |
- | CALL SetRAMBank | + | |
- | + | ||
- | LD A, 0x01 | + | |
- | | + | |
; Esperamos una pulsación de teclas antes de empezar: | ; Esperamos una pulsación de teclas antes de empezar: | ||
- | | + | |
; Ahora vamos a cambiar de nuevo al banco 0, leemos el valor que | ; Ahora vamos a cambiar de nuevo al banco 0, leemos el valor que | ||
- | ; hay en 0xc000 | + | ; hay en $c000 y lo representamos en pantalla. Recordemos que |
- | ; acabamos de escribir | + | ; acabamos de escribir |
- | ; y que en su momento pusimos | + | ; y que en su momento pusimos |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Esperamos una pulsación de teclas: | ; Esperamos una pulsación de teclas: | ||
- | | + | |
; Ahora vamos a cambiar de nuevo al banco 1, leemos el valor que | ; Ahora vamos a cambiar de nuevo al banco 1, leemos el valor que | ||
- | ; hay en 0xc000 | + | ; hay en $c000 y lo representamos en pantalla. Recordemos que |
- | ; acabamos de leer 0xA antes de cambiar de banco, y que en su | + | ; acabamos de leer A antes de cambiar de banco, y que en su |
- | ; momento pusimos | + | ; momento pusimos |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; Esperamos una pulsación de teclas: | ; Esperamos una pulsación de teclas: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
- | ; SetRAMBank: Establece un banco de memoria sobre 0xc000 | + | ; SetRAMBank: Establece un banco de memoria sobre $c000 |
- | ; Entrada: B = banco (0-7) a paginar entre 0xc000-0xffff | + | ; Entrada: B = banco (0-7) a paginar entre $c000-$ffff |
; | ; | ||
SetRAMBank: | SetRAMBank: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
Línea 250: | Línea 250: | ||
; | ; | ||
ClearScreen: | ClearScreen: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ;--- Libreria utils.asm --- | + | |
- | INCLUDE " | + | INCLUDE " |
- | END 35000 | + | |
</ | </ | ||
Línea 275: | Línea 275: | ||
| | ||
- | A continuación podemos ver una rutina, que nos permitirá detectar si el modelo de Spectrum que estamos ejecutando es 48K o 128K. Para saber si estamos en un modelo 48K o en un modelo 128K, simplemente tratamos de paginar escribiendo y recuperando valores en la dirección $C000, antes y después de paginar dicho banco, para ver si el valor cambia o no. Según el resultado de esta operación, establecemos a 1 o no la variable de memoria | + | A continuación podemos ver una rutina, que nos permitirá detectar si el modelo de Spectrum que estamos ejecutando es 48K o 128K. Para saber si estamos en un modelo 48K o en un modelo 128K, simplemente tratamos de paginar escribiendo y recuperando valores en la dirección $c000, antes y después de paginar dicho banco, para ver si el valor cambia o no. Según el resultado de esta operación, establecemos a 1 o no la variable de memoria |
<code z80> | <code z80> | ||
- | ORG 35000 | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; | ; | ||
Línea 298: | Línea 298: | ||
; | ; | ||
CHECK_128: | CHECK_128: | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; en un 128k mantendra el valor original => Z | ; en un 128k mantendra el valor original => Z | ||
; en un 48k el contenido de HL estara invertido => NZ | ; en un 48k el contenido de HL estara invertido => NZ | ||
- | | + | |
; Si NZ => 48K => salimos sin tocar el " | ; Si NZ => 48K => salimos sin tocar el " | ||
- | | + | |
; tiene paginacion de memoria es un 128k, +2, +2AB/E o +3/E | ; tiene paginacion de memoria es un 128k, +2, +2AB/E o +3/E | ||
- | | + | |
- | | + | |
fin_check_128: | fin_check_128: | ||
- | | + | |
- | | + | |
es_128k | es_128k | ||
; | ; | ||
- | ROM_CLS | + | ROM_CLS |
- | ROM_STACK_BC | + | ROM_STACK_BC |
- | ROM_PRINT_FP | + | ROM_PRINT_FP |
- | END 35000 | + | |
</ | </ | ||
Si ejecutamos este programa, veremos aparecer en pantalla un " | Si ejecutamos este programa, veremos aparecer en pantalla un " | ||
- | Esta rutina, ejecutada al principio de nuestro programa, establecerá pues la variable | + | Esta rutina, ejecutada al principio de nuestro programa, establecerá pues la variable |
Línea 364: | Línea 364: | ||
El problema es que mientras el haz de electrones del monitor avanza redibujando la imagen, el Spectrum no puede interrumpirlo (de hecho, no puede controlarlo, | El problema es que mientras el haz de electrones del monitor avanza redibujando la imagen, el Spectrum no puede interrumpirlo (de hecho, no puede controlarlo, | ||
- | Esto implica que cuando la ULA está " | + | Esto implica que cuando la ULA está " |
- | Por eso, los programas que corren en el bloque de 16KB entre $4000 (16384) y $7FFF (32767), o pretenden acceder a la misma justo cuando la ULA quiere leer algún dato gráfico de la VRAM, pueden resultar más lentos en ejecución cuando la ULA está leyendo la pantalla. | + | Por eso, los programas que corren en el bloque de 16KB entre $4000 (16384) y $7fff (32767), o pretenden acceder a la misma justo cuando la ULA quiere leer algún dato gráfico de la VRAM, pueden resultar más lentos en ejecución cuando la ULA está leyendo la pantalla. |
Como se detalla en la FAQ de comp.sys.sinclair alojada en World Of Spectrum, este efecto sólo se da cuando se está dibujando la pantalla propiamente dicha, ya que para el trazado del borde la ULA proporciona al haz de electrones el color a dibujar y no se accede a memoria, por lo que no se produce este retraso o delay. | Como se detalla en la FAQ de comp.sys.sinclair alojada en World Of Spectrum, este efecto sólo se da cuando se está dibujando la pantalla propiamente dicha, ya que para el trazado del borde la ULA proporciona al haz de electrones el color a dibujar y no se accede a memoria, por lo que no se produce este retraso o delay. | ||
- | | + | |
Como podéis imaginar, este es uno de los mayores quebraderos de cabeza para los programadores de emuladores, y es la principal causa (junto con la **Contended I/O**, su equivalente en cuanto a acceso de puertos, también producido por la ULA), de que en los primeros tiempos de la emulación, no todos los juegos fueran correctamente emulados con respecto a su funcionamiento en un Spectrum. También muchas demos con complejas sincronizaciones y timings dejaban de funcionar en emuladores de Spectrum que no implementaban estos " | Como podéis imaginar, este es uno de los mayores quebraderos de cabeza para los programadores de emuladores, y es la principal causa (junto con la **Contended I/O**, su equivalente en cuanto a acceso de puertos, también producido por la ULA), de que en los primeros tiempos de la emulación, no todos los juegos fueran correctamente emulados con respecto a su funcionamiento en un Spectrum. También muchas demos con complejas sincronizaciones y timings dejaban de funcionar en emuladores de Spectrum que no implementaban estos " | ||
- | En nuestro caso, como programadores, | + | En nuestro caso, como programadores, |
Para que los retrasos de la ULA no afecten a nuestro programa, podemos poner al principio del mismo (en esos 6768 bytes iniciales) todas las rutinas que utilicemos al principio del mismo o que su ejecución no sea vital en cuanto a tiempos: funciones para el menú del juego, detección de 128K/48K, rutinas de redefinición del teclado, textos de los menúes y del fin del juego, etc. | Para que los retrasos de la ULA no afecten a nuestro programa, podemos poner al principio del mismo (en esos 6768 bytes iniciales) todas las rutinas que utilicemos al principio del mismo o que su ejecución no sea vital en cuanto a tiempos: funciones para el menú del juego, detección de 128K/48K, rutinas de redefinición del teclado, textos de los menúes y del fin del juego, etc. | ||
Línea 383: | Línea 383: | ||
===== Contended Memory + Paginación | ===== Contended Memory + Paginación | ||
- | | + | |
* Modelos +2/128K : Bancos 1, 3, 5 y 7. | * Modelos +2/128K : Bancos 1, 3, 5 y 7. | ||
Línea 412: | Línea 412: | ||
\\ | \\ | ||
- | ===== Paginación de memoria desde Z88DK (C) ===== | ||
- | | + | ===== En resumen ===== |
- | <code c> | + | |
- | //--- SetRAMBank ------------------------------------------------------ | + | |
- | // | + | |
- | // Se mapea el banco (0-7) indicado sobre $C000. | + | |
- | // | + | |
- | // Ojo: aqui no se deshabilitan las interrupciones | + | |
- | // de usar el registro B, se usa un parametro tomado desde la pila. | + | |
- | // En caso de ser importante la velocidad, se puede usar " | + | |
- | // el parametro | + | |
- | // | + | |
- | void SetRAMBank(char banco) | + | |
- | { | + | |
- | #asm | + | |
- | | + | |
- | ld hl, 2 | + | |
- | add hl, sp | + | |
- | ld a, (hl) | + | |
- | ld b, a | + | Esto es así porque el Z80 tiene un bus de direcciones de 16 bits, por lo que no es capaz de direccionar más de 65535 bytes. Por eso, para acceder a más de 65535 bytes necesitamos " |
- | ld A, ($5B5C) | + | |
- | and F8h | + | |
- | or B | + | |
- | ld BC, $7FFD | + | |
- | ld ($5B5C), A | + | |
- | out (C), A | + | |
- | # | + | |
- | } | + | |
- | </ | + | |
- | Con el anterior código podemos mapear uno de los bancos de memoria de 16KB sobre la página | + | Es nuestra responsabilidad paginar |
- | + | ||
- | * 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 | + | |
- | + | ||
- | \\ | + | |
- | < | + | |
- | /* Allocate space for the stack */ | + | |
- | #pragma output STACKPTR=24500 | + | |
- | </ | + | |
- | \\ | + | |
- | + | ||
- | 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). | + | |
- | + | ||
- | + | ||
- | \\ | + | |
- | ===== En resumen ===== | + | |
- | Comprendiendo | + | Una vez tenidas en cuenta estas consideraciones, |
- | Así, podemos almacenar los datos de diferentes niveles en diferentes bloques, y cambiar de uno a otro mediante paginación en el momento adecuado. Esto permite realizar cargas de datos desde cinta almacenando la totalidad de los datos del juego o programa en bancos libres de memoria y convertir nuestro juego multicarga (con una carga por fase) en un juego de carga única (con todos los elementos del juego almacenados en memoria), evitando el tedioso sistema de rebobinar y volver a cargar la primera fase cuando el jugador muere. | + | Así, por ejemplo, podemos almacenar los datos de diferentes niveles en diferentes bloques, y cambiar de uno a otro mediante paginación en el momento adecuado. Esto permite realizar cargas de datos desde cinta almacenando la totalidad de los datos del juego o programa en bancos libres de memoria y convertir nuestro juego multicarga (con una carga por fase) en un juego de carga única (con todos los elementos del juego almacenados en memoria), evitando el tedioso sistema de rebobinar y volver a cargar la primera fase cuando el jugador muere. |
- | Ahora bastará con que nuestro programa, una vez cargado en memoria y en ejecución, pagine un determinado bloque, cargue 16K-datos sobre él, pagine otro bloque diferente, y realice otra carga de datos desde cinta, y así sucesivamente con todos los bloques de datos del juego. Estas cargas de datos podemos hacerlas bien desde nuestro programa " | + | En modo 128K bastará con que nuestro programa, una vez cargado en memoria y en ejecución, pagine un determinado bloque, cargue 16K-datos sobre él, pagine otro bloque diferente, y realice otra carga de datos desde cinta, y así sucesivamente con todos los bloques de datos del juego. Estas cargas de datos podemos hacerlas bien desde nuestro programa " |
El resultado: 128KB de memoria a nuestro alcance, tanto para cargar múltiples datos gráficos o de mapeado sobre ellos como para llenarlos internamente desde nuestro programa. | El resultado: 128KB de memoria a nuestro alcance, tanto para cargar múltiples datos gráficos o de mapeado sobre ellos como para llenarlos internamente desde nuestro programa. |