PARTE II
50/60 HzSe pueden encontrar MSX en los cinco continentes, eso implica que el código que uno haga, debe (o debería) de tener en cuenta los diferentes velocidades de sincronismo bajo las cuales van a funcionar las máquinas. Las combinaciones que podemos encontrar, incluyendo el estandar de TV son las siguientes:
- Japón: NTSC (60Hz)
- Reino Unido: PAL (50Hz)
- EEUU: NTSC (60Hz)
- Francia: SECAM (50Hz)
- Alemania: PAL (50Hz)
- Rusia: NTSC (60Hz)
- España: PAL (50Hz)
Como internacional se toma la combinación PAL/50Hz.
Si nos encontramos en un equipo MSX2 o superior podremos averiguar la velocidad del refresco leyendo el bit 1 del registro 9 del VDP, si contiene 1 estamos en una máquina a 50Hz, si vale 0 estamos en una funcionando a 60Hz (obtenemos el mismo resultado consultando el bit 7 de la posición $2B). De igual forma podremos cambiar la velocidad de refresco modificando el contenido de dicho bit en el registro. La opción a seguir, que el programa se adapte al refresco o bien que nosotros modifiquemos el refresco para adaptarlo al funcionamiento de nuestro programa......; bien, eso ya es algo a decidir por parte del programador y un tema bastante complejo, más adelante comentaré las implicaciones que puede tener el refresco al trabajar sobre la pantalla.
La BIOSPoco y mucho que decir
, la existencia de la BIOS viene determinada por la necesidad de que exista un interfaz que abstraiga las diferencias en el hardware de manera que un desarrollador no necesite adaptar su código a dichas diferencias. Si queremos estar seguros que nuestro código vaya a funcionar en el 100% de las máquinas deberá utilizar siempre la BIOS, de no hacerlo así tendremos que estar muy seguros de lo que hacemos y testearlo en el mayor número de máquinas posible....y aún así nunca podremos estar seguros
.
El R800 y el T9769CEl R800 fue el microprocesador diseñado por ASCII para que los equipos TurboR de Panasonic pudieran mantener la compatibilidad con el Z80 oficial impuesto por la norma y disfrutar a la vez de todas las prestaciones de un micro de "casi" 16 bits. La velocidad de la CPU es de 7,16Mhz; pero debido a que la mayoría de las instrucciones se ejecutan en sólo 1 ciclo, a diferencia de los 4 ciclos de media en un Z80 estandar, el resultado práctico es el de tener un Z80 a 28,64Mhz.
Esto da como resultado que si ejecutamos un programa concebido para que se ejecute a los "humildes" 3,57Mhz del Z80 que incorporan el resto de los modelos de MSX, vamos a encontrarnos con que todo va a ir 4 veces más rápido si no hemos tenido la precaución de sincronizarlo con el barrido de pantalla.
Del programador depende decidir si aprovecha esto a su favor o no.
Si el programa definitivamente debe de ejecutarse a su velocidad original, no quedará otro remedio que desactivar el R800 mediante una simple llamada a la BIOS de los TurboR:
CHGCPU: equ $0180
ld a,$80
call CHGCPU
Un posible ejemplo de rutina de detección en la que también se maneja información acerca de la familia y de la frecuencia de refresco, podría quedar como sigue (gracias a Dioniso por el código):
CHGCPU: equ $0180
GETCPU: equ $0183
VDP_09: equ $FFE8
;MSX Version
ld a,($2d)
or a
jr z,MSX1
cp 3 ;turbo-r
jr nz,guess_hz
call GETCPU
or a
jr z,guess_hz
ld a,$80
call CHGCPU ;modo z80 en turbo-r
;detectamos herzios
guess_hz:
ld a,1
ld (MODELO),a ;MSX 2 o superior - podemos modificar los herzios.
ld a,(VDP_09) ; leemos el contenido del registro 9 del VDP
bit 1,a
jr z,its_60hz
xor a
ld (HERZIOS),a ; 0=50hz (variable para herzios)
jr end_check_version
its_60hz:
ld a,1
ld (HERZIOS),a ;1=60hz
jr end_check_version
MSX1:
xor a
ld (MODELO),a
end_check_version:
Con ello obtendriamos en la variable MODELO, el modelo de MSX (0=MSX1, 1=MSX2 ó superior) y en la variable HERZIOS, la velocidad de refresco (0=50Hz, 1=60Hz)
Si no queremos modificar permanentemente la velocidad podemos optar por mantenerla parcialmente; como ejemplo podríamos tomar una rutina de cálculo compleja, en este caso activariamos el R800 al no acceder a la pantalla y así aprovechar esa velocidad extra de proceso.
Otro de los problemas relacionados con el uso del R800 es que no es del todo 100% compatible con las características
no oficiales del Z80, más concretamente los bits 3 y 5 del registro F (que normalmente contienen la copia o variantes de los respectivos bits 3 y 5 del registro A) no tienen el mismo comportamiento. Igualmente mnemónicos no oficiales como SLL han desaparecido, éste concretamente han sido reemplazado por TST, que tampoco es oficial.
La conclusión es que lo mejor que se puede hacer es limitarse a usar en lo posible las operaciones documentadas por Zilog y dejar de lado las no oficiales (aunque en algunos casos sea tentador usar un LD IXL,a
).
Similar al problema de velocidad que plantea el R800, es el caso del T9769C de Toshiba que incorporan los modelos de Panasonic MSX2+, este micro puede funcionar a 2 velocidades, 3.57MHz y 5.37MHz. Es evidente que aunque nos parezca atractiva la posibilidad de activar el modo "turbo", no debemos desarrollar nuestro software específicamente para que corra a esta velocidad; sería muy lento para el resto de los mortales...
Tengo sobrefrecuenciado mi Pentium 4, ¿como puedo hacer lo mismo con mi MSX??
Como ya he dicho, esto se podrá hacer únicamente sobre los modelos de Panasonic FS-A1FX/WX/WSX. A través del acceso a un par de puertos exclusivos de estos modelos, podremos conmutar la velocidad de la CPU:
($40) = 8 (ID Panasonic)
($41) bit 0: Velocidad CPU (0=5.37MHz, 1=3.57MHz)
Ejemplo (obtenido del MAP) de como se activaría el modo turbo: ld a,8
out ($40),a ; se envía el código de fabricante 8 (Panasonic) al puerto $40
in a,($40) ; se lee el valor que acabamos de enviar
cpl ; complemento de todos los bits de ese valor
cp 8 ; si no volvemos a obtener el valor originalmente enviado (8),
jr nz,Not_WX ; no se trata de un WX/WSX/FX, no hay modo turbo :(.
xor a ; si lo es, escribimos 0 en el puerto $41
out ($41),a ; y activamos el modo de alta velocidad
Puertos de acceso al VDPAunque en el 99,99% de los casos se trata de los puertos $98 y $99, la norma determina que pueden ser otros y su valor estará definido en VDP.DR ($0006) y VDP.DW ($0007). Según el TH esta sería la tabla de equivalencias de los puertos de video en el MSX :
Para
todos los MSX:
----------------------------------------------------------------------
| Port | Address | Function |
----------------------------------------------------------------------
| port #0 (READ) | n | read data from VRAM |
| port #0 (WRITE) | n' | write data to VRAM |
| port #1 (READ) | n + 1 | read status register |
| port #1 (WRITE) | n'+ 1 | write to control register |
----------------------------------------------------------------------
Exclusivamente para modelos superiores a un MSX1:
----------------------------------------------------------------------
| Port | Address | Function |
----------------------------------------------------------------------
| port #2 (WRITE) | n'+ 2 | write to palette register |
| port #3 (WRITE) | n'+ 3 | write to indirectly specified register |
----------------------------------------------------------------------
Siendo
n el valor obtenido a partir de VDP.DR y
n' el obtenido de VDP.DW.
Si se quiere hacer un juego 100% compatible, nuestro código deberá leer el contenido de estas variables y utilizar los valores obtenidos como puertos de acceso al VDP en el resto de programa. El ejemplo aplicado a un SETWRT:
ld a,[7]
ld c,a
inc c
ld a,l
di
out (c),a
ld a,h
and $3F
or $40
ei
out (c),a
Si no recurrimos a código automodificable, esto supone una pérdida importante de velocidad y por eso es habitual el uso directo de los puertos estandar.
Acceso contenido a la VRAM (o como acceder a la VRAM sin usar la BIOS y no morir en el intento)Uno de los casos típicos en los que puede que decidamos obviar el uso de la BIOS (es un poco lenta) es el del acceso a la VRAM. En la mayoria de los modelos de MSX1, dicho acceso a la VRAM debe de hacerse con contención ya que la velocidad del Z80 a la hora de enviar los datos es superior a la del TMS9918/28/29 para procesarlos. En los TMS9938/58 que incorporan las siguientes generaciones de MSX, no es necesario esperar entre acceso y acceso; pero si queremos que nuestro programa funcione, además de hacerlo en modelos MSX2 o superiores, en cualquier MSX1, tendremos que respetar este método.
Este retardo entre acceso y acceso (OUT) al VDP, en términos de ciclos del Z80, deberá ser equivalente a 28 ciclos.
¿Y por qué hay que esperar 28 ciclos?
Según el datasheet oficial del TMS9918, el retardo base en los accesos al VDP es de 2 microsegundos, por lo que en principio habrá que calcular cuantos ciclos de espera supondrán para un Z80 a 3,579545Mhz.
Si tenemos que:
1T/3579545 = 0,000000279 segundos/ciclo
Entonces ya podemos saber a cuantos ciclos equivalen esos 2 microsegundos:
0,000002/0,000000279 = 7,16845 ciclos
Así pues, suponen 7,16 ciclos del Z80 de los MSX; hay que esperar entre 7-8 ciclos de media. Además de esto en función del modo de pantalla habrá que esperar (o no) un poco más antes de poder acceder al dato en la VRAM (tanto da leer como escribir). Para screen 2 tenemos un retardo extra que va de 0 a 6 microsegundos, por lo que si convertimos a ciclos, en el peor de los casos tendremos que esperar hasta 28T-29T. La excepción la tenemos únicamente durante una ventana que dura aproximadamente 4300 microsegundos (a 60Hz) tras el retrazado vertical, en ese intervalo el retardo extra es cero. Durante ese intervalo basta con esperar esos 7-8 ciclos iniciales.
Asumiendo esta limitación y si tomamos como ejemplo el OUT más práctico (que no el más rápido), el envio de datos sucesivos seguiría este patrón:
outi
nop
nop
outi
nop
nop
...
...
...
Como el tiempo empleado en el OUTI hay que tenerlo en cuenta (18 ciclos), nos bastaría con añadir otros 10 ciclos más (2 NOP de 5 ciclos cada uno) para garantizar el correcto funcionamiento en cualquier modelo de MSX1 y en cualquier momento del refresco de pantalla. Se puede ajustar algo más y reducir los NOPs a sólo uno; pero bueno, si queremos estar 100% seguros tendremos que hacerlo así.
Antes he comentado que existe una ventana de 4300 microsegundos tras el retrazado vertical cuando el refresco es de 60Hz, en ese intervalo los datos pueden ser enviados sin retardos extras y a la máxima velocidad posible. ¿Cuantos bytes podriamos transferir teóricamente durante ese intervalo?. Para calcularlo primero averiguamos cuantos ciclos tenemos disponibles durante ese lapso de tiempo :
(4,3ms * 3579545) / 1000ms = 15392 ciclosSabemos que un OUTI consume 18 ciclos:
15392 / 18 (ciclos OUTI) = 855 instrucciones OUTIAsí pues, podriamos encadenar 855 OUTIs uno detrás de otro sin esperas, lo que da poco más de 1/6 de pantalla o 3 lineas y pico de caracteres. A partir de ahí ya no tenemos ninguna garantía de que no se produzca corrupción del flujo de datos, tendremos que empezar a intercalar retardos. Este resultado es aproximado, en el siguiente apartado se tratará de calcular con más precisión este valor.
Por cierto, en este intervalo podriamos usar un OTIR (marginado él); pero claro ya nos podriamos ir olvidando de esos 855 bytes; eso sí, a costa de la pérdida de velocidad ganaríamos bastante memoria.
Diferencias en el acceso al VDP en equipos con refresco 50Hz/60HzSiguiendo con el punto anterior, si estamos aprovechando el retrazado vertical para volcar datos a granel tendremos que tener en cuenta de la frecuencia a la que estemos trabajando, 50Hz ó 60Hz. Estas diferencias están determinadas por el tratamiento específico que tiene el VDP de la pantalla según corresponda:
Para los equipos funcionando a 60Hz (realmente son 59,94Hz), tenemos que la pantalla está diferenciada en estas zonas:
Linea Descripcion
192 Pantalla activa
24,5 Borde inferior
3 Intervalo inferior
3 Intervalo vertical
13 Intervalo superior
27 Borde superior
------
262,5 lineas en total
Si dejamos de lado las 192 que conforman la pantalla activa, nos quedan 70,5 lineas durante las cuales podremos trabajar sin retardos.
Ahora calculamos cuantos ciclos de CPU corresponden a cada linea:
1 pantalla / 60 = 0,016683350016683350016683350016683 seg.
1 linea = 0,016683350016683350016683350016683 seg / 262,5 = 0,0000635556 seg.La duración total del retrazado será:
0,0000635556 * 70.5 = 0,0044806711473378140044806711473378 seg. de VBlank (4480 microsegundos, más o menos los 4300 del datasheet )Como previamente ya habiamos calculado a cuantos segundos equivalía 1T, tenemos que:
0,0000635556 / 0,000000279 = 227,79T por lineay finalmente:
227,79T * 70.5 = 16059,748T por VBlankResumiendo:
1 linea = 0,0000635556 seg. = 227,8T
1 VBlank = 70,5 lineas = 16059,75T Ya podemos calcular el número de OUTIs:
16059,75T / 18 = 892 OUTIsEsta es la peor de las situaciones ya que es la que deja menos tiempo libre al Z80. Lo recomendable pues, es desarrollar teniendo en mente la compatibilidad y restricciones de velocidad de un refresco a 60Hz y así hacer más fácil la posterior adaptación a PAL.
Con un refresco de 50Hz, las zonas serán las mismas; pero no así su extensión:
Linea Descripcion
192 Pantalla activa
48,5 Borde inferior
3 Intervalo inferior
3 Intervalo vertical
13 Intervalo superior
54 Borde superior
------
313,5 lineas
En el caso del PAL, nos quedan 121,5 lineas durante las cuales podremos trabajar sin retardos.
Ahora calculamos cuantos ciclos de CPU corresponden a cada linea:
1 pantalla = 0,02 seg.
1 linea = 0,02 seg / 313,5 = 0,0000637958 seg.La duración total del retrazado será:
0,0000637958 * 121,5 = 0,0077511961 seg. de VBlank (7751 microsegundos)Ahora calculamos los T por linea:
0,0000637958 / 0,000000279 = 228,65T por lineaDeberían salir idénticas cantidades si los valores de 1/50 y 1/59,94 fueran exactos
); pero el baile de decimales continua...
y finalmente:
228,65T * 121,5 = 27782,06T por VBlankResumiendo nuevamente:
1 linea = 0,0000637958 seg. = 228,65T
1 VBlank = 121,5 lineas = 27782,06T Ya podemos calcular el número de OUTIs:
27782,06 / 18 = 1543 OUTIsEstá claro que la frecuencia de 50Hz nos da mucho más margen; pero es mejor no acostumbrase...
** Gracias a ARTRAG por corregir los valores del número de lineas por fotograma y por los valores más aproximados de refresco
**
Bibliografía en la web:ASCII MSX Technical HandbookTMS9918A/TMS9928A/TMS9929A DatasheetSega Master System VDP documentationZ80 Flag AffectionMSX I/O ports overviewMSX1 Memory MapMSX2 Memory Map