Consideraciones para el diseño de la interface
Valoración de usuario: / 4
PeorMejor 
Sábado, 11 de Octubre de 2008 04:03

Precisamente, debido a su popularidad, hemos decidido realizar una implementación de este chip de tal forma que sea compatible con el software escrito para MSX. Esto significa que nuestra interface usará para comunicarse con el Z80A los puertos 98h y 99h, y generará una señal de interrupción en cada retrazo vertical del TMS.

En los siguientes apartados veremos que son precisamente estos dos requerimientos los que "chocan" con algunas peculiaridades del hardware del ZX Spectrum, pero también veremos que ha sido posible solucionarlos.

El diseño no se ha hecho pensando en abaratar costes: quiero decir que seguramente puedan ahorrarse uno o incluso dos circuitos integrados y no perder funcionalidad. Sin embargo, y dado que el esquema presentado es un prototipo, se ha decidido ir "sobre seguro", huyendo en la medida de lo posible de determinadas "tretas electrónicas".

English version is in progress. In the meanwhile, please use this Google automated translation version. It may be a little odd to read, but at least, you will get an idea of what's going on, if you can't read spanish.

Acceso a puertos con dirección par:

El ZX Spectrum fue diseñado casi con el único objetivo de abaratar costes. Esto llegó a los extremos de usar sólo un bit del bus de direcciones para decodificar la dirección de puerto ocupada por el único periférico del ZX Spectrum, la ULA. Oficialmente, la ULA usa el puerto FEh, o 254 decimal.

Esta dirección de puerto, escrita en binario, es 11111110. Esto es, el bit 0 bajado, y los demás subidos. El bit 0 hace referencia, cuando se habla de direcciones de puertos, a la línea A0 del bus de direcciones, y es ésta precisamente la única línea usada en la decodificación.

Esto significa que en realidad, cualquier dirección con A0 = 0 provocará que la ULA se active para suministrar o aceptar un dato, lo cual es un engorro, ya que se pierde la mitad del espacio de direcciones de puerto: todas las direcciones pares seleccionarán a a ULA como periférico. Esta decisión de diseño choca con uno de los requerimientos de nuestra interface: el poder usar el puerto 98h.

Afortunadamente Sinclair pensó en esa posibilidad: en el bus trasero existe una señal denominda IORQGE. Dicha señal está a 1 para indicar que la ULA no está seleccionada, o a 0 para indicar que sí lo está. En la siguiente figura se muestra un detalle del esquemático del Spectrum donde se genera dicha señal.

Detalle del esquemático del Spectrum, issue 3B, mostrando parte de la ULA. IORQ y A0 vienen del Z80A. R27 junto con TR6 forman un decodificador simple que entrega un 0 al pin 33 de la ULA cuando A0=0 y IORQ=0: en realidad, el pin 33 de la ULA toma el valor de tensión que haya en IORQ siempre que TR6 esté en corte; si éste se satura, en su emisor aparecen 5V independientemente del valor de IORQ. TR6 se satura si en su base hay 5V, lo que ocurre cuando A0=1. R27 limita la intensidad que circula por IORQ de forma que si TR6 se satura pero IORQ vale 0, no se dañe esa línea en el Z80A por cortocircuito. Si se fuerzan 5 voltios en el pin 33 de la ULA, la situación es equivalente a tener TR6 saturado. Este pin 33 es el que sale al bus trasero como IORQGE (IORQULA en esta versión del esquemático).

Un periférico externo puede forzar un nivel alto en esa señal, de tal forma que la ULA no se selecciona. No obstante, si el periférico externo no quiere influir en dicha señal, debe desconectarse de ella. En nuestro diseño, esto se realiza mediante uno de los cuatro buffers triestado que se pueden encontrar en un 74125.

Esquema de un buffer triestado. La señal de control de triestado HAB, determina si la entrada estará conectada a la salida o no.

Este elemento deja pasar el valor que hay en su entrada a su salida, sin alteraciones, pero sólo cuando la señal de control de triestado está a nivel bajo (0 voltios). Si la señal de control está a nivel alto, la salida se desconecta de la entrada, como si hubiera un circuito abierto entre ambas (estado de alta impedancia). En nuestro circuito, la entrada la conectaremos a 5 voltios (nivel alto), y la salida, a IORQGE en el bus trasero.

La señal de control del triestado se gobierna desde la lógica de decodificación de la interface: si se detecta un intento de acceso a algún puerto par de los usados en la interface, dicha lógica envía un 0 a la señal de control, con lo que el buffer se abre y "deja pasar" los 5 voltios a su salida, forzando a IORQGE a un nivel alto, y deseleccionando así a la ULA.

El uso de un 74125 para esta misión puede sonar excesivo, dado que con un transistor puede lograrse el mismo propósito. Veremos en el siguiente apartado que realmente necesitamos un buffer del tipo de los que hay en un 74125 en la interface, por lo que se decidió aprovechar otro de ellos para controlar IORQGE.

Dos opciones para controlar IORQGE. A la izquierda con un transistor NPN de uso general, llevando la base (HAB) a +5 voltios (1 lógico) éste se satura y en el emisor aparecen los 5 voltios del colector, llevando a IORQGE a nivel alto, deseleccionando la ULA. Con el buffer triestado a la derecha, llevando la entrada HAB a nivel alto, se abre el buffer y los 5 voltios de la entrada aparecen en la salida que va a IORQGE, deseleccionando la ULA.

Aún así, podemos seguir teniendo problemas. Resulta que IORQGE existe y es funcional sólo en los modelos anteriores al 128K. Para los demás, la señal IORQGE aparece nombrada en el conector trasero pero no está conectada a ningún sitio, o simplemente, ni se nombra; aparece como NC.

Sin embargo, esto también es solucionable: Para el ZX Spectrum 128K "heatsink" y el +2 gris, Yarek Adamski propone una modificación usando un par de transistores y resistencias. Para poder probar la interface en un 128K, se ha realizado dicha modificación pero usando sólo un transistor NPN convencional (BC239). La base del transistor se conecta mediante un hilo de wire-wrapping al pin 13A, donde debería encontrarse IORQGE. Una resistencia de 10K une ese pin a masa. El colector del transistor se lleva a +5 voltios, y el emisor al extremo superior de R27, que está unido precisamente al pin IORQ de la ULA. En los modelos españoles, como el de la foto, en los que no hay transformador toroidal, hay mucho espacio para implementar esta modificación.

Modificación realizada a un 128K modelo "œheatsink" para habilitar la señal IORQGE. En el esquemático, los elementos a añadir están en rojo.

La modificación de Yarek usa dos transistores para inhibir mediante IORQGE no sólamente la ULA, sino también la lógica de conmutación de bancos de memoria y el PSG. Si se realiza sólo con un transistor como se ha descrito aquí, será necesario usar el ordenador en modo 48K para que el conmutador de bancos de memoria quede inhibido permanentemente y no se active accidentalmente al usar el puerto 98h.

El Spectrum +2A/+3 carece también de esta señal. Es posible obtenerla de forma muy sencilla: en el esquemático del +2A/+3, la señal IORQ de la ULA está conectada directamente a su homónima en el Z80A. La modificación consiste en cortar esa conexión directa y sustituirla por una conexión a través de una resistencia de 330 ohmios. El extremo de esa resistencia que está conectado a la ULA se llevará mediante cable de wire-wrapping al pin 13A del conector de expansión. Un 1 en dicho pin hará que la ULA (y toda la circuitería de conmutación de bancos y PSG) se deseleccione. La resistencia de 330 ohmios aisla la señal IORQ del lado del procesador, que podrá estar a nivel bajo sin problemas, para seleccionar nuestra interface.

Esquema de la modificación necesaria en un +2A/+3. La resistencia debe añadirse cortando antes la pista que en el esquema original une el pin 20 del Z80A con el 80 de la ULA.

La mayor integración de la placa del +2A nos obliga a ser más cuidadoso. Un buen punto para cortar la conexión IORQ_Z80 -> IORQ_ULA es el mostrado en la figura: con un cutter se corta la pista con cuidado de no dañar las pistas vecinas, y se suelda la resistencia aprovechando las dos vías preexistentes en la placa. El extremo soldado en la vía más cercana al Z80A es el que se llevará a B13.

Detalle de las vías (flechas blancas) donde han de soldarse los extremos de la resistencia de 330 ohmios. La flecha roja indica la pista que hay que cortar antes de añadir la resistencia.

Generación de la interrupción por retrazo vertical:

Hay tareas que no deben realizarse mientras el TMS está leyendo la VRAM para generar la imagen de video, como por ejemplo cambiar la definición de un sprite. Es por ello que habitualmente, el código que se encarga de hacer estas cosas se ejecuta como parte de la rutina de interrupción del MSX, que se dispara precisamente en el comienzo del retrazo vertical, cuando el TMS no está leyendo la memoria.

El ZX Spectrum ya dispone de un mecanismo similar, que dispara una interrupción en el retrazo vertical de la ULA. En un primer momento se pensó que esto era suficiente, pero al ver los "glitches" que aparecían en pantalla, por culpa de actualizar sprites durante una interrupción no sincronizada con el retrazo vertical del TMS, se optó por incluir la lógica necesaria para incorporar estas interrupciones.

La señal de interrupción INT aparece en el bus trasero, y está cableada internamente de tal forma que el nivel lógico aplicado en esta señal desde el bus trasero tendrá más prioridad que el nivel lógico que entregue la ULA. Esto es cierto para los modelos de Spectrum excepto el +2A/+3. En éstos hay que hacer una modificación similar a la explicada para IORQGE.

Acoplamiento entre la salida de interrupción de la ULA y la señal INT del Z80A, mediante R26.

En los modelos soportados, o en el +2A/+3 después de hacer la modficación pertienente, si se fuerza un 1 en el pin INT del conector trasero, la interrupción de la ULA no llegará al Z80A, y si se fuerza un 0, ocurrirá una interrupción aunque la ULA en ese momento no esté generando ninguna. Si esta señal se desconecta de nuesto periférico, volverá a ser la ULA quien gobierne el nivel lógico de la misma.

Volvemos por tanto a recurrir a uno de los buffers triestado del 74125. La entrada del buffer está conectada a la patilla de petición de interrupción del TMS, y la salida, a la señal INT del bus trasero. Cuando se aplique un 0 en la señal de control, será el TMS quien gobierne las interrupciones en el Spectrum, y cuando se aplique un 1, será la ULA.

Para que desde un programa se pueda elegir qué dispositivo será el generador de interrupciones, se han añadido en la interface dos puertos más: el puerto BEh y el puerto BFh: accediendo (leyendo o escribiendo cualquier valor) al puerto BFh se elige al TMS como generador de interrupciones, y accediendo a BEh, se elige a la ULA.

La implementación de estos puertos es muy sencilla: desde la lógica de control y decodificación de direcciones de la interfaz se detecta cuando se está accediendo a BEh o BFh. Un acceso a cualquiera de estos dos puertos pone a nivel alto un señal que va a parar a la entrada de reloj (CLKBI) de un biestable contenido en un 7474. La entrada D del biestable se conecta a A0 (etiquetada como DATABI), de tal forma que cuando se accede a BEh, el biestable almacena un 0, y cuando se accede a BFh, se almacena un 1.

Detalle del esquemático de la interface de video, mostrando el biestable 7474 y el buffer triestado 74125.

Por supuesto, en el caso de acceso a BEh, se activa también la lógica de inhibición de la ULA, poniendo un nivel alto en IORQGE.

La salida \Q del biestable es la señal de control del buffer triestado: si el biestable almacena un 1, el buffer se abre, permitiendo interrupciones desde el TMS. Si almacena un 0, el buffer se cierra, dejando a la ULA enviar sus peticiones de interrupción.

En el programa, cuando se quiere hacer uso de las interrupciones, se hará un acceso, por ejemplo, un OUT al puerto BFh. Previamente se habrá definido el nuevo gestor de interrupción de la manera acostumbrada (IM 2).

Este gestor tendrá que reconocer la interrupción generada, leyendo el registro de estado del TMS. De no hacerse, la señal INT seguirá estando a 0. Dado que en el Z80A las interrupciones son disparadas por nivel y no por flanco, un estado permanente de 0 voltios en esa línea provocaría que la rutina de interrupción fuera llamada una y otra vez. Por tanto, no se debe activar el control de interrupciones por TMS hasta no haber instalado una rutina de gestión de interrupciones que se encargue de reconocer la interrupción del TMS.

Al instalar la nueva rutina de gestión de interrupciones hay que tener en cuenta un detalle: cuando se instala un gestor para el modo IM 2, usando a la ULA como generador de interrupciones, sabemos que ésta se genera durante el retrazo vertical. En este período, la ULA no lee la memoria de pantalla, lo que significa que el bus de datos está en estado de alta impedancia. Las resistencias de pull-up que existen en las líneas del bus de datos del Spectrum hacen que el valor del bus sea FFh cuando está en ese estado. Este comportamiento es harto conocido, y es por eso que las rutinas de interrupción se definen en direcciones de memoria que son de la forma I*256+255. Por ejemplo, haciendo I=253, la dirección de la rutina de interrupción debe almacenarse a partir de la posición 65023.

Pero si la fuente es el TMS, éste generará la interrupción de forma independiente al estado de la ULA, lo que significa que, seguramente, ésta ocurra mientras la ULA está leyendo la memoria, momento en el cual el bus de datos contiene el valor del byte que está leyendo. El comportamiento del bus de datos es similar a cuando se realiza una lectura al puerto 255.

El resultado de todo esto es que ya no se puede confiar en que la dirección de la rutina de interrupción esté en I*256+255, sino que ahora estará en I*256+DB donde DB es el contenido del bus de datos en ese momento.

La solución a este fenómeno también es conocida: se rellenan todos los posibles vectores de interrupción con el mismo valor de dirección: valor que tener idénticos su byte alto y su byte bajo, ya que la expresión I*256+DB puede apuntar a cualquier posición de byte dentro de su rango.

A continuación se muestra un listado con el código necesario para instalar satisfactoriamente una rutina de interrupción compatible con el comportamiento del TMS9929 y el comportamiento del bus de datos del Spectrum. La rutina comienza en la etiqueta INTTMS, y rellena un bloque de 257 bytes con el valor FEh, y hace que el comienzo de la tabla de vectores de interrupción apunte al comienzo de dicho bloque. Para cualquier valor de DB, la expresión I*256+DB apuntará a un valor de 16 bits igual a FEFEh, que será precisamente la dirección de comienzo de la rutina de gestión de interrupciones (GESTINT). La rutina de este ejemplo realiza el reconocimiento de interrupción al TMS y después ejecuta la rutina estándar de lectura de teclado del Spectrum.

La etiqueta INTULA contiene una rutina que vuelve a dar el control de las interrupciones a la ULA.

INTTMS:   DI
          LD HL,0FD00h
          LD DE,0FD01h
          LD BC,256
          LD (HL),0FEh
          LDIR
          LD A,0FDh
          LD I,A
          IM 2
          OUT (0BFh),A  ;acceso a BFh para activar interrupciones por TMS. 
                        ;Da igual el valor de A.
          EI
          RET

INTULA:   DI
          OUT (0BEh),A  ;habilita a la ULA para emitir interrupciones
          IM 1          ;volvemos al modo IM 1
          EI
          RET

          ORG 0FEFEh
          DEFW GESTINT

GESTINT:  PUSH AF
          IN A,(99h) ;reconocimiento de la interrupción.
          ;
          ; gestión de sprites, etc. del TMS
          ;
          RST 38h
          POP AF
          EI
          RETI
 

Add your comment

Your name:
Your email:
Título:
Comment:
  The word for verification. Lowercase letters only with no spaces.
Word verification:
ZX Projects, Powered by Joomla! and designed by SiteGround web hosting