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:rutinas_save_load [07-01-2024 19:49] – sromero | cursos:ensamblador:rutinas_save_load [19-01-2024 11:58] – [Ejemplo completo] sromero | ||
---|---|---|---|
Línea 8: | Línea 8: | ||
===== Formato de los datos en cinta ===== | ===== Formato de los datos en cinta ===== | ||
- | | + | |
- | Un SAVE produce 2 bloques de datos en la cinta: | + | |
\\ | \\ | ||
Línea 45: | Línea 45: | ||
|< 70% 20% 20% 60% >| | |< 70% 20% 20% 60% >| | ||
^ Byte ^ Longitud ^ Descripción ^ | ^ Byte ^ Longitud ^ Descripción ^ | ||
- | | 0 | 1 | Byte Flag ($00 ó $FF) | | + | | 0 | 1 | Byte Flag ($00 ó $ff) | |
| 1 | 1 | Tipo de bloque (0-3)| | | 1 | 1 | Tipo de bloque (0-3)| | ||
| 2 | 10 | Nombre de fichero (rellenado con espacios en blanco) | | | 2 | 10 | Nombre de fichero (rellenado con espacios en blanco) | | ||
Línea 55: | Línea 55: | ||
| | ||
- | El byte de flag (Byte 0) y el de Checksum (byte 18) no forman parte exactamente de la cabecera, sino del bloque cargado en sí mismo (también están presente cuando cargamos datos y no cabeceras), pero se han incluído dentro de la tabla para hacerla de lectura más sencilla. Puede decirse que el byte-flag es el byte prefijo de todo bloque de datos (considerando una cabecera de 17 bytes como un bloque de datos) y el checksum es el byte sufijo de ese mismo bloque. Concretamente, | + | El byte de flag (Byte 0) y el de Checksum (byte 18) no forman parte exactamente de la cabecera, sino del bloque cargado en sí mismo (también están presente cuando cargamos datos y no cabeceras), pero se han incluído dentro de la tabla para hacerla de lectura más sencilla. Puede decirse que el byte-flag es el byte prefijo de todo bloque de datos (considerando una cabecera de 17 bytes como un bloque de datos) y el checksum es el byte sufijo de ese mismo bloque. Concretamente, |
El byte de tipo de bloque indica qué datos se van a cargar a continuación, | El byte de tipo de bloque indica qué datos se van a cargar a continuación, | ||
Línea 105: | Línea 105: | ||
</ | </ | ||
- | Así pues, la rutina de la ROM del Spectrum se encarga (tanto al grabar como al leer) de codificar pulsos de diferentes duraciones para almacenar los ceros y unos de forma consecutiva. Nosotros aprovecharemos (como veremos a continuación) dicha rutina para cargar o salvar bloques de datos a nuestro antojo sin tener que programar esas temporizaciones y lecturas/ | + | Así pues, la rutina de la ROM del Spectrum se encarga (tanto al grabar como al leer) de codificar pulsos de diferentes duraciones para almacenar los ceros y unos de forma consecutiva. Nosotros aprovecharemos (como veremos a continuación) dicha rutina para cargar o salvar bloques de datos a nuestro antojo sin tener que programar esas temporizaciones y lecturas/ |
El nivel más bajo al que necesitamos llegar es el siguiente: | El nivel más bajo al que necesitamos llegar es el siguiente: | ||
Línea 116: | Línea 116: | ||
* Cada bloque tiene la siguiente estructura lógica (formato de los datos DENTRO de un bloque): | * Cada bloque tiene la siguiente estructura lógica (formato de los datos DENTRO de un bloque): | ||
- | * Flag byte, con un valor de $00 para bloques de cabecera o $FF para bloques de datos. | + | * Flag byte, con un valor de $00 para bloques de cabecera o $ff para bloques de datos. |
* Los datos en sí mismos: 17 bytes para cabeceras, o la longitud concreta de los datos para los bloques de datos. | * Los datos en sí mismos: 17 bytes para cabeceras, o la longitud concreta de los datos para los bloques de datos. | ||
- | * Un byte de checksum, calculado de forma que haciendo un XOR de todos los bytes juntos, incluyendo el flag Byte, produzca $00. | + | * Un byte de checksum, calculado de forma que haciendo un '' |
| | ||
Línea 128: | Línea 128: | ||
* Los 1s y los 0s se almacenan en cinta como pulsos de duraciones concretas. | * Los 1s y los 0s se almacenan en cinta como pulsos de duraciones concretas. | ||
* Las rutinas de la ROM nos permiten leer y escribir en cinta bloques de datos, realizando ellas la temporización adecuada para convertir nuestros "datos en memoria" | * Las rutinas de la ROM nos permiten leer y escribir en cinta bloques de datos, realizando ellas la temporización adecuada para convertir nuestros "datos en memoria" | ||
- | * Sólo necesitaríamos escribir una rutina propia de carga (que detecte pulsos, temporice, etc) si quisiéramos programar nuestra propia carga, por ejemplo para cargar a diferente velocidad que el Spectrum (ultracargas), | + | * Sólo necesitaríamos escribir una rutina propia de carga (que detecte pulsos, temporice, etc) si quisiéramos programar nuestra propia carga, por ejemplo para cargar a diferente velocidad que el Spectrum (ultracargas), |
\\ | \\ | ||
===== Ejemplo de volcado de un SAVE ===== | ===== Ejemplo de volcado de un SAVE ===== | ||
- | En la FAQ de comp.sys.sinclair y WorldOfSpectrum tenemos un ejemplo muy interesante que muestra el formato lógico de los datos grabados con un SAVE en cinta. Supongamos el siguiente comando BASIC: | + | En la FAQ de comp.sys.sinclair y WorldOfSpectrum tenemos un ejemplo muy interesante que muestra el formato lógico de los datos grabados con un '' |
< | < | ||
Línea 198: | Línea 198: | ||
* "D BREAK - CONT repeats" | * "D BREAK - CONT repeats" | ||
- | | + | |
- | | + | |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 213: | Línea 213: | ||
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 224: | Línea 224: | ||
==== Rutina de SAVE de la ROM ==== | ==== Rutina de SAVE de la ROM ==== | ||
- | La rutina SAVE de la ROM tiene unos parámetros muy similares a la de LOAD, y está alojada en 1218d (04c2h): | + | La rutina |
|< 70% 20% 80% >| | |< 70% 20% 80% >| | ||
Línea 242: | Línea 242: | ||
En ocasiones, podemos ignorar el bloque de cabecera totalmente, sobre todo cuando sabemos qué vamos a cargar desde cinta, qué destino tiene, y qué tamaño tiene, y lo especificamos directamente en nuestro programa ASM. En ese caso, podemos cargar la cabecera con el CARRY FLAG a cero (verify), con lo cual la leemos pero no la almacenamos en memoria, y después cargar los valores adecuados en IX, DE, A, etc, poner el CF a 1, y cargar los datos que vienen tras la cabecera. | En ocasiones, podemos ignorar el bloque de cabecera totalmente, sobre todo cuando sabemos qué vamos a cargar desde cinta, qué destino tiene, y qué tamaño tiene, y lo especificamos directamente en nuestro programa ASM. En ese caso, podemos cargar la cabecera con el CARRY FLAG a cero (verify), con lo cual la leemos pero no la almacenamos en memoria, y después cargar los valores adecuados en IX, DE, A, etc, poner el CF a 1, y cargar los datos que vienen tras la cabecera. | ||
- | | + | |
<code z80> | <code z80> | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
</ | </ | ||
Línea 266: | Línea 266: | ||
Lo primero que necesitamos saber es, ¿cómo convertimos nuestros datos (gráficos, pantalla de carga, números, tablas precalculadas, | Lo primero que necesitamos saber es, ¿cómo convertimos nuestros datos (gráficos, pantalla de carga, números, tablas precalculadas, | ||
- | Para empezar, podemos hacerlo desde el mismo BASIC del Spectrum, usando el comando SAVE: esto nos permitirá grabar datos de memoria en cinta: | + | Para empezar, podemos hacerlo desde el mismo BASIC del Spectrum, usando el comando |
<code basic> | <code basic> | ||
Línea 276: | Línea 276: | ||
| | ||
- | Así, nuestro | + | Así, nuestro |
Pero en dicho TAP o TZX tenemos que añadir (al final del mismo) los datos que el programa espera cargar. Imaginemos que estos datos son una pantalla gráfica (.scr) de 6912 bytes. Tendremos un fichero " | Pero en dicho TAP o TZX tenemos que añadir (al final del mismo) los datos que el programa espera cargar. Imaginemos que estos datos son una pantalla gráfica (.scr) de 6912 bytes. Tendremos un fichero " | ||
Línea 286: | Línea 286: | ||
</ | </ | ||
- | Este fichero de 2 bytes de tamaño (inicio_rom.bin, | + | Este fichero de 2 bytes de tamaño ('' |
< | < | ||
Línea 323: | Línea 323: | ||
</ | </ | ||
- | | + | |
- | Para convertir un fichero .bin en un fichero tap sin cabecera, creamos un pequeño programa ASM (rom.asm) como el siguiente: | + | Para convertir un fichero .bin en un fichero tap sin cabecera, creamos un pequeño programa ASM ('' |
< | < | ||
Línea 331: | Línea 331: | ||
</ | </ | ||
- | A continuación, | + | A continuación, |
< | < | ||
Línea 337: | Línea 337: | ||
</ | </ | ||
- | Con esto, obtendremos un fichero | + | Con esto, obtendremos un fichero |
\\ | \\ | ||
Línea 356: | Línea 356: | ||
</ | </ | ||
- | Ahora ya tenemos una pantalla SCR guardada en formato TAP (en cinta). Nótese cómo podríamos cargar este TAP desde BASIC con un //LOAD "" | + | Ahora ya tenemos una pantalla SCR guardada en formato TAP (en cinta). Nótese cómo podríamos cargar este TAP desde BASIC con un '' |
Lo siguiente que necesitamos es el programa propiamente dicho, el cual hará la carga de la pantalla en videomemoria: | Lo siguiente que necesitamos es el programa propiamente dicho, el cual hará la carga de la pantalla en videomemoria: | ||
Línea 368: | Línea 368: | ||
ORG 32000 | ORG 32000 | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | END 32000 | + | |
</ | </ | ||
Al respecto del código fuente, como habréis notado, realizamos 2 llamadas a la rutina de la ROM. La primera carga (pero no almacena en ningún sitio) el primer bloque de datos existente (la cabecera de la pantalla de carga). La rutina de la ROM ignorará esta carga porque el CARRY FLAG está a cero (0=VERIFY). La segunda llamada a 1366 realizará la carga de los datos propiamente dichos. Al cargarlos sobre la dirección de destino 16384 (la dirección de la videoram), veremos cómo se van cargando sobre la pantalla directamente desde la cinta. | Al respecto del código fuente, como habréis notado, realizamos 2 llamadas a la rutina de la ROM. La primera carga (pero no almacena en ningún sitio) el primer bloque de datos existente (la cabecera de la pantalla de carga). La rutina de la ROM ignorará esta carga porque el CARRY FLAG está a cero (0=VERIFY). La segunda llamada a 1366 realizará la carga de los datos propiamente dichos. Al cargarlos sobre la dirección de destino 16384 (la dirección de la videoram), veremos cómo se van cargando sobre la pantalla directamente desde la cinta. | ||
- | | + | |
- | * Un TAP (loadscr.tap) con nuestro programa (pero que no tiene datos después de él). | + | * Un TAP ('' |
- | * Un TAP (zxcolumns.tap) con los datos gráficos (en este caso, una pantalla completa de 6912 bytes). | + | * Un TAP ('' |
- | Si cargamos en el Spectrum, o en un emulador, el fichero loadscr.tap, | + | Si cargamos en el Spectrum, o en un emulador, el fichero |
< | < | ||
Línea 395: | Línea 395: | ||
</ | </ | ||
- | Ahora sí, cargando | + | Ahora sí, cargando |
\\ | \\ | ||
Línea 413: | Línea 413: | ||
===== La rutina LD-BYTES (Load) ===== | ===== La rutina LD-BYTES (Load) ===== | ||
- | A continuación podemos ver el código desensamblado de la rutina LD-BYTES ($0556), la que estamos utilizando para la carga de datos desde cinta. Es una rutina muy interesante y disponer de su código fuente puede tener 2 usos directos. | + | A continuación podemos ver el código desensamblado de la rutina |
El primero es poder comprender de forma exácta cómo funciona la carga de datos desde cinta y los tiempos que se manejan en dicha carga. El segundo es el de reproducir la rutina en nuestro programa y añadir funciones adicionales como un contador de carga o incluso algún tipo de minijuego durante la misma. | El primero es poder comprender de forma exácta cómo funciona la carga de datos desde cinta y los tiempos que se manejan en dicha carga. El segundo es el de reproducir la rutina en nuestro programa y añadir funciones adicionales como un contador de carga o incluso algún tipo de minijuego durante la misma. | ||
- | El código comentado está extraído del documento "The Complete Spectrum ROM Disassembly", | + | El código comentado está extraído del documento "//The Complete Spectrum ROM Disassembly//", de Ian Logan y Frank O' |
- | <code z80> | + | <code z80> |
; THE ' | ; THE ' | ||
; This subroutine is called to LOAD the header information (from 07BE) | ; This subroutine is called to LOAD the header information (from 07BE) | ||
; and later LOAD, or VERIFY, an actual block of data (from 0802). | ; and later LOAD, or VERIFY, an actual block of data (from 0802). | ||
0556 LD-BYTES: | 0556 LD-BYTES: | ||
- | | + | |
- | ; cannot hold +FF.) | + | ; cannot hold $FF.) |
- | | + | |
- | ; header and +FF for a block of | + | ; header and $FF for a block of |
; data. | ; data. | ||
; The carry flag is reset for | ; The carry flag is reset for | ||
; VERIFYing and set for | ; VERIFYing and set for | ||
; LOADing. | ; LOADing. | ||
- | | + | |
- | | + | |
; disabled. | ; disabled. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | ; (+22 for ' | + | ; ($22 for ' |
; - the present EAR state.) | ; - the present EAR state.) | ||
- | | + | |
; The first stage of reading a tape involves showing that a pulsing | ; The first stage of reading a tape involves showing that a pulsing | ||
; signal actually exist (i.e. ' | ; signal actually exist (i.e. ' | ||
- | 056B LD-BREAK | + | 056B LD-BREAK |
; being pressed. | ; being pressed. | ||
- | 056C LD-START | + | 056C LD-START |
- | | + | |
; approx. 14,000 T states. But if | ; approx. 14,000 T states. But if | ||
; an ' | ; an ' | ||
Línea 463: | Línea 463: | ||
; that the signal is still pulsing. | ; that the signal is still pulsing. | ||
- | | + | |
- | 0574 LD-WAIT | + | 0574 LD-WAIT |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; period. | ; period. | ||
; Now accept only a ' | ; Now accept only a ' | ||
- | 0580 LD-LEADER | + | 0580 LD-LEADER |
- | | + | |
- | | + | |
; period. | ; period. | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; been found. | ; been found. | ||
; After the leader come the ' | ; After the leader come the ' | ||
- | 058F LD-SYNC | + | 058F LD-SYNC |
- | | + | |
- | | + | |
- | | + | |
- | CP | + | CP |
- | | + | |
- | | + | |
- | | + | |
; (Return carry flag reset.) | ; (Return carry flag reset.) | ||
Línea 501: | Línea 501: | ||
; VERIFied. But the first byte is the type flag. | ; VERIFied. But the first byte is the type flag. | ||
- | | + | |
- | XOR +03 ; on will be BLUE & YELLOW. | + | XOR $03 ; on will be BLUE & YELLOW. |
- | | + | |
- | | + | |
; byte to zero. | ; byte to zero. | ||
- | | + | |
; flag byte. | ; flag byte. | ||
- | | + | |
; LOADING loop. | ; LOADING loop. | ||
Línea 516: | Línea 516: | ||
; the last byte is the ' | ; the last byte is the ' | ||
- | 05A9 LD-LOOP | + | 05A9 LD-LOOP |
- | | + | |
; handling the first byte. | ; handling the first byte. | ||
- | | + | |
; tape. | ; tape. | ||
- | | + | |
; required. | ; required. | ||
- | | + | |
; next byte. | ; next byte. | ||
- | 05B3 LD-FLAG | + | 05B3 LD-FLAG |
; place temporarily. | ; place temporarily. | ||
- | | + | |
- | | + | |
; tape. (Carry flag reset.) | ; tape. (Carry flag reset.) | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; after the jump. | ; after the jump. | ||
Línea 540: | Línea 540: | ||
; tested against the original byte. | ; tested against the original byte. | ||
- | 05BD LD-VERlFY | + | 05BD LD-VERlFY |
- | | + | |
- | | + | |
; flag reset.) | ; flag reset.) | ||
; A new byte can now be collected from the tape. | ; A new byte can now be collected from the tape. | ||
- | 05C2 LD-NEXT | + | 05C2 LD-NEXT |
- | 05C4 LD-DEC | + | 05C4 LD-dec |
- | | + | |
- | | + | |
- | 05C8 LD-MARKER | + | 05C8 LD-MARKER |
; from a ' | ; from a ' | ||
; The ' | ; The ' | ||
- | 05CA LD-8-BITS | + | 05CA LD-8-BITS |
; ' | ; ' | ||
- | | + | |
; exceeded. (Carry flag reset.) | ; exceeded. (Carry flag reset.) | ||
- | | + | |
; approx. 2,400 T states; resetting | ; approx. 2,400 T states; resetting | ||
- | | + | |
; setting it for a ' | ; setting it for a ' | ||
- | | + | |
; register. | ; register. | ||
- | | + | |
; next bit. | ; next bit. | ||
- | | + | |
; bits to be fetched. | ; bits to be fetched. | ||
; The ' | ; The ' | ||
- | | + | |
- | | + | |
- | | + | |
; Passes round the loop are made until the ' | ; Passes round the loop are made until the ' | ||
; At that point the ' | ; At that point the ' | ||
- | | + | |
- | | + | |
- | | + | |
- | | + | |
; byte. | ; byte. | ||
- | CP | + | CP |
- | | + | |
; (Carry flag reset if in error.) | ; (Carry flag reset if in error.) | ||
Línea 599: | Línea 599: | ||
; is required and LD-EDGE-1 is used to find the time before the next ' | ; is required and LD-EDGE-1 is used to find the time before the next ' | ||
- | 05E3 LD-EDGE-2 | + | 05E3 LD-EDGE-2 |
- | | + | |
; is an error. | ; is an error. | ||
- | 05E7 LD-EDGE-1 | + | 05E7 LD-EDGE-1 |
- | 05E9 LD-DELAY | + | 05E9 LD-DELAY |
- | | + | |
- | | + | |
; The sampling loop is now entered. The value in the B register is | ; The sampling loop is now entered. The value in the B register is | ||
; incremented for each pass; ' | ; incremented for each pass; ' | ||
- | 05ED LD-SAMPLE | + | 05ED LD-SAMPLE |
- | | + | |
; ' | ; ' | ||
- | | + | |
- | IN A,(+FE) ; i.e. BREAK & EAR. | + | IN A,($fe) ; i.e. BREAK & EAR. |
- | | + | |
- | | + | |
; if BREAK was pressed. | ; if BREAK was pressed. | ||
- | | + | |
- | AND +20 ; 'last edge-type'; | + | AND $20 ; 'last edge-type'; |
- | | + | |
; A new ' | ; A new ' | ||
; So change the border colour and set the carry flag. | ; So change the border colour and set the carry flag. | ||
- | | + | |
- | | + | |
- | | + | |
- | AND +07 ; Keep only the border colour. | + | AND $07 ; Keep only the border colour. |
- | OR | + | OR |
- | OUT (+FE),A ; Change the border colour (RED/ | + | OUT ($fe),A ; Change the border colour (RED/ |
; CYAN or BLUE/ | ; CYAN or BLUE/ | ||
- | | + | |
- | | + | |
; Note: The LD-EDGE-1 subroutine takes 465 T states, plus an additional 58 T | ; Note: The LD-EDGE-1 subroutine takes 465 T states, plus an additional 58 T | ||
Línea 641: | Línea 641: | ||
; allowance is made for ten additional passes through the sampling loop. | ; allowance is made for ten additional passes through the sampling loop. | ||
; The search is thereby for the next edge to be found within, roughly, | ; The search is thereby for the next edge to be found within, roughly, | ||
- | ; 1,100 T states (465 + 10 * 58 + overhead). This will prove successful | + | ; 1,100 T states (465 $ 10 * 58 $ overhead). This will prove successful |
; for the sync ' | ; for the sync ' | ||
</ | </ | ||
+ | |||
+ | |||
\\ | \\ | ||
===== Ficheros ===== | ===== Ficheros ===== |