CC51B - Arquitectura de Computadores : Tarea #4
El ZX Spectrum 48K
por Denis Fuenzalida
( Introducción / Lenguaje de Máquina / Interrupciones / Memoria / Canales / Punto Flotante )
El Z80 y las interrupciones
El Z80 tiene 3 modos de interrupciones, seleccionadas por las instrucciones de máquina IM 0, IM 1 e IM 2. En el modo 1, el procesador simplemente ejecuta una instrucción RST #38 si se pide una interrupcion. Este el modo en que generalmente se encuentra el Spectrum.
El otro modo que generalmente se usa es IM 2. Si se pide una interrupción a la CPU, el procesador primero construye una dirección de 16 bits, combinando el registro I (como el byte más significativo), con lo que se encuentre en el bus de datos en ese momento. Luego, se ejecuta la subrutina apuntada por esta dirección de 16 bits. Rodnay Zaks en su libro " Programming the Z80 " dice que solo pueden usarse bytes pares como byte-menos-significativo, pero esto no es cierto. El Spectrum normal no tiene hardware para poner un byte determinado en el bus de datos, y por esto, el bus siempre se leerá como FF (porque la ULA tampoco lee la pantalla si se produce una interrupción). Así entonces, la dirección de salto resultante es de la forma 256*I + 255. Sin embargo, algunos periféricos no muy limpios pueden poner valores en el bus de datos donde no deberían, con lo que los programas más nuevos no asumen que el byte del bus sea FF. El parche era colocar una tabla de 257 bytes iguales, desde la dirección 256*i, y poner la rutina de la interrupción en una dirección multiplo de 257 (!!!).
Un truco útil, pero no muy usado es usar una tabla que contenga FFs (o usar algunos de la ROM), y poner un byte 18(hex), que es el código para el assembler JR, en la dirección FFFF. El primer byte de la ROM es un F3(hex), que equivale a un DI, con lo que el salto JR saltará a la dirección FFF4, donde un se debe poner un salto absoluto JP a la dirección donde finalmente está la rutina de la interrupción.
En el modo de interrupciones cero, el procesador ejecuta la instrucción que el dispositivo que interrumpe ha puesto en el bus de datos. En un Spectrum típico, coincidentemente ( :-) ), habrá un byte FF, que es el código para una RST #38. Pero, por la razón del párrafo anterior, esto no es seguro.
Dos flip-flops (IFF1 e IFF2) son seteados o reseteados por las instrucciones EI (enable interruptions) y DI (disable interruptions). El IFF1 determina que interrupciones se permiten, pero su valor no puede leerse. El valor del IFF2 se copia al flag P/V, haciendo un LD A,I y luego LD A,R. Cuando una NMI (interrupción no enmascarable) ocurre, el IFF1 se resetea, por lo tanto, se deshabilitan futuras interrupciones enmascarables, pero el IFF2 permanece sin cambios. Esto le permite a la rutina de servicio de NMI el chequear si el programa recién interrumpido ha permitido o prohibido las interrupciones enmascarables. La rutina de NMI deberá terminar con la instrucción RETN, que en adición a la instrucción de retorno RET, además copia el contenido de IFF2 a IFF1, con lo que se recupera el estado de interrupción del programa interrumpido.
Cuando ocurre una interrupcion, la instrucción en curso tiene que ser completada primero. Así, el comienzo de la interrupción depende relativamente del comienzo del frame hasta el largo de la última instrucción en T-states. Si el procesador estaba ejecutando un HALT (que es, en efecto una serie infinita de NOPs), la rutina de interrupción comienza a lo más en 3 T-states más alla del comienzo del frame. Por supuesto, el procesador también necesita algunos T-states poner el program counter en el stack, leer el vector de interrupción y saltar a la rutina. En los modos de interrupción 0 y 1 , el tiempo total tomado para alcanzarla dirección #38 es de 13 ciclos. En el modo 2, el tiempo para alcanzar la rutina de interrupción es de 19 ciclos. Esto puede interpretarse como sigue:
5 ciclos: para leer el bus de datos (esto se desecha si estamos en IM 1) 6 ciclos: para leer el vector de interrupcion de 2 bytes (sólo en IM 2) 2 ciclos: para doble decrementar el SP 6 ciclos: para hacer un push del Program Counter y poner el nuevo.
(este es el orden en que se cree que estas operaciones ocurren, pero esto no ha sido testeado).
Cuando ocurre una NMI, el tiempo total en llegar a la dirección #0066 es de 11 ciclos; esto se interpreta como 5 ciclos en reconocer la interrupción (esto incluye acceso a la memoria, pero el resultado se desecha), y entonces 6 ciclos para hacer un push del PC.
Las interrupciones son gatilladas por un pin en el chip del Z80, que la ULA del Spectrum 48K mantiene en 0 por precisamente 32 T-states. Este valor del pin se copia tras cada instrucción, excepto por EI, DI y las instrucciones con prefijos para IX e IY (prefijos DD y FD).
Cuando la linea /NMI se baja, in flip-flop interno en el Z80 se enciende para notar que hay una NMI pendiente. Este flip-flop se copia al final de cada instrucción, excepto las prefijos DD/FD y posiblemente por las instrucciones EI,DI.
Este pin debe mentenerse abajo por lo menos por 23 T-states, pues algunas instrucciones como las que involucran a IX e IY pueden tomar 23 T-states. Si la rutina de intrerrupción comienza con las instrucciones EI / NOP , esto puede causar una doble interrupción, debido a que el pin /INT debe ser copiado 19 (por el IM 2) + 4 + 4 = 27 T-states después de ser copiado por primera vez, y entonces deberá seguir abajo.