6502 vs 6510 Parte 12 – Como funcionan las interrupciones

Continuamos este estudio comparativo del 6502 vs el 6510 esta vez estudiando qué son las interrupciones.

Vamos a estudiar qué son las interrupciones,  los distintos tipos que poseen los procesadores 6502 y 6510, en que pines se encuentran, cómo responde el procesador a cada una de ellas y cuál es la mejor forma de diseñar un programa para qué puedan ser utilizadas para hablar con los periféricos.

Qué son las interrupciones

Las interrupciones son la forma que tenemos desde el hardware para avisarle al procesador que necesitamos su atención. La capacidad total del sistema puede ser expandida y aprovechada mucho mejor si el procesador sólo ejecuta el software para hablar con los periféricos en casos estrictamente necesarios, este es el objetivo de todo el sistema de interrupciones.

Generalmente hay dos tipos de eventos que se prestan para ser resueltos mediante interrupciones, eventos que suceden muy de vez en cuando (como el presionar una tecla) y eventos que suceden en paralelo junto a otras tareas del procesador (como dibujar los caracteres en las pantallas del procesador).

Existen dos tipos diferentes las interrupciones enmascarables IRQ (Interrupt Request)  y las no enmascarables NMI (Non Maskable Interrupt), atendidas por los pines IRQ 4 y NMI 6 del 6502 y los pines IRQ 3 y NMI 4 del 6510, respectivamente.

Las interrupciones sólo pueden ser atendidas por el procesador cuando este termina el procesamiento de la instrucción que está actualmente ejecutando, en el peor de los casos una interrupción que ocurra durante una operación de read/write/modify deberá esperar 14 ciclos (7 ciclos de procesamiento + 7 ciclos que tarda el procesador en configurar el ambiente par ala ejecución de una interrupción), antes de que sea atendida.

Interrupciones enmascarables

Estas son identificadas por el procesar al recibir un LOW en el pin IRQ (<0,4V), el cuál es el pin 4 del 6502 y el pin 3 del 6510 y siempre y cuando no esté en uno el bit 2 Interrupt Disable Flag del Processor Status Register, lo que enmascararía (deshabilitaría) la interrupción.

El procesador mantendrá la atención para resolver los pedidos de interrupción durante todo el tiempo que este pin está en LOW y la interrupción no esté enmascarada.

En este momento el procesador, esperar que termine la instrucción que está ejecutando  y seguidamente ejecutará el programa para atender a este tipo de interrupción, la dirección inicial del programa va a buscarla a las posiciones de memoria $FFFE y $FFFF que debemos recordar son little endian con lo que si ubicamos nuestro programa manejador de interrupciones (interrupt handler) en la dirección $0600 se verá en la memoria de la siguiente forma:

AddressContenido
$FFFE$00
$FFFF$06

Este programa debe atender al dispositivo que pidió la interrupción y este dispositivo cuando satisfizo su requerimiento debe liberar la línea de IRQ.

Para atender la interrupción el procesador primero activa el Flag de deshabilitar Interrupción (bit 2 del PSR) lo que deshabilita futuras interrupciones enmascarables (BRK e INT), guarda el Processor Status Register y la dirección de regreso del actual program counter al stack.

Luego coloca en el Program Counter la dirección del contenido del Vector $FFFE y $FFFF.

Si no hay más pedidos de interrupción el procesador retoma el programa anterior en la posición de memoria que sigue al pedido de interrupción como si hubiera sido interrumpido por un jump, pero conservando todos los valores del Processor Status Register que tenía el procesador antes de la interrupción.

Si los registros del Acumulador, X e Y no fueron preservados al hacer el servicio de la interrupción estos se pierden.

Nuestro programa en código máquina para dar servicio a una interrupción es muy parecido a una subrutina pero retornamos utilizando el comando rti. Por ejemplo:

Este programa incrementa un contador de 16 bits, el mismo incrementa la posición de memoria apuntada por counter y cada vez que esta vuelve a cero incrementa la siguiente posición en counter + 1. Recordemos que counter no es el valor del contador sino la dirección de memoria en donde se encuentra ese valor.

Este programa no conserva ninguno de los registros A,X o Y, un mejor programa que sí los conserva sería.

Bloqueando o Enmascarando la Interrupción Enmascarable

¡Aquel que enmascare la interrupción enmascarable buen enmascarador será! (que trabalengua). Si no quisiéramos ser interrumpidos en el caso de pedido a una interrupción durante alguna operación crítica que estuviéramos realizando podemos desactivar el flag de interrupciones del Processor Status Register.

Para esto puedo usar el comando SEI (Set Interrupt Disable Status) que cambia a 1 el valor del flag I bit 2 del Processor Status Register.

Para volver a habilitar las interrupciones utilizamos el comando CLI (clear interrupt disable bit) que pone en 0 el bit I.

Esto lo utilizaremos por ejemplo al tratar de sumar utilizando dos posiciones de memoria en múltiples instrucciones como en este ejemplo que usamos un contador de 16 bits. Si tuviéramos una interrupción en el medio del proceso la suma quedaría en forma inestable por eso en este programa desactivamos las interrupciones, sumamos y luego las volvemos a activar.

Cuándo sucede una interrupción el procesador automáticamente pone en 1 el FLAG de Interrupt Disable Flag del Processor Status Register, evitando de esta forma que se atienda dos veces la misma interrupción o que una interrupción interrumpa el programa que atienda a las interrupciones (salvo que sea una NMI).

La instrucción RTI automáticamente limpia el flag de Interrupt Disable ya que copia el Processor Status Register que grabó anteriormente en el stack y lo regresa y como vuelve de atender una interrupción es lógico que el PCR anterior tenía el Flag I en cero.

Interrupciones No Enmascarables

Las interrupciones no enmascarables son activadas por el pin NMI el pin 6 en el 6502 y el pin 4 en el 6510, son active low por lo que esperan un valor menor a 0,4 volts para activarse.

Esta interrupción no espera que se mantenga en LOW su pin sino que se activa en la transición de High a Low y sólo se sirve una vez, deberá volver a high (>2,4 volts) y luego a low para activarse nuevamente.

Utiliza como Vector $FFFA para el low byte y $FFFB para el High byte.

AddressContenido
$FFFA$00
$FFFB$08

La interrupción NMI puede interrumpir a una interrupción común si quisiéramos y es el evento de mayor prioridad dentro del procesador.

Al activarse el procesador marca un flag interno por el cual deja que termine de ejecutarse la instrucción en curso y al principio de la próxima instrucción realiza la interrupción, guardando el PCR, y la dirección a la cual regresar.

 Luego de esto irá a ejecutar el código ubicado en la posición de memoria $0800 en nuestro ejemplo.

¿Se pueden deshabilitar?, no nunca, siempre el procesador va a responder y ejecutar el programa en el vector de interrupciones, peeeeero, podemos hacer un truquito. Si ese programa apunta a una línea de código que contenga la instrucción RTI, volveremos a seguir ejecutando el programa original antes de que sucediera la interrupción NMI. El procesador la ejecutó y la interrupción sucedió pero en la práctica nada pasó salvo perder algunos ciclos de procesador.

Qué pasa adentro del procesador

Si queremos realmente profundizar y ver como el CPU decrementa el stack, podemos usar un proyecto como visual6502 que nos muestra como funcionan las señales internas del procesador.

Cuando sucede una interrupción el procesador realiza algunos pasos automáticos que están codificados dentro de un área interna llamada Decode ROM, estos son los pasos realizados:

  • Recibe la señal low en el pin correspondiente a la interrupción (NMI o IRQ) y el flag de Interrupt Disable Flag del procesador está en cero para el caso de las interrupciones IRQ.
  • Espera a que termine la instrucción que se está ejecutando en ese momento
  • Push del high byte del Program Counter al Stack
  • Push del low byte del Program Counter al Stack
  • Push del Processor Status Register al Stack
  • Traer el Program Counter Low byte desde el vector indirecto ($FFFA para NMI y $FFFE para IRQ)
  • Traer el Program Counter High byte desde el vector indirecto ($FFFB para NMI y $FFFF para IRQ)
  • Configurar El address bus con las direcciones High y Low que contenían los vectores de interrupción
  • Comienza el programa para atender la interrupción (todos los pasos que siguen a partir de aquí están determinados por software)

Pero tanto IRQ y NMI son dos pines no son una instrucción con código, ¿Cómo sabe el procesador todo lo que tiene que hacer?. Bueno con un truquito.

Si miramos dentro del procesador las instrucciones son preprocesadas dentro del módulo de Predecode Logic y luego obtenemos un número de instrucción para el Instruction Register que se corresponde a las instrucciones clásicas de Assembler (por ejemplo A9 para el load al acumulador de un valor).

Al observar este módulo el mismo recibe dos señales internas del procesador (no son pines) llamada RESG y INTG , cuando sucede una interrupción a través del contacto Assert Interrupt Control se registra un valor cero y esto hace que la Predecode Logic envíe un cero al Instruction Register, él mismo interpreta que tiene que correr la operación que posee como código un cero y ¿Cuál es esta?

La operación BRK la cuál corre una interrupción como definición agregando también el byte B (flag 5 del PCR) en 1 si fue llamado como una instrucción BRK y sigue en 0 si fue llamado por una interrupción en la línea IRQ o RESET.

Para saber qué estado tiene el Flag debemos leer el Stack ya que este flag está guardado como parte del PCR que fue grabado durante la llamada a la interrupción en el Stack, con el siguiente código podemos analizar el estado del Flag.

El BRK es una INT realizada por Software y la INT corre el mismo opcode que es el 00 (circular y lógico?) En resumen ambas son casi la misma instrucción una activada por hardware y la otra por software.

Como siempre para ver en más profundidad el tema los invito a ver el Diagram de Hanson y el proyecto visual6520 ambos links incluidos como referencia.

Cómo funciona en el Commodore 64

Estudiaremos qué está conectado a las líneas IRQ y NMI de procesador 6510 y, qué programas se ejecutan en los vectores de IRQ y NMI.

Interrupción no enmascarable NMI programas y funcionamiento

En el Commodore 64 tenemos conectados a la línea NMI pin 4 del procesador 6510  varias cosas:

  • El pin D del puerto de expansión,
  • El circuito de doble timer 556 U20 que está conectado entre otras cosas a la tecla RESTORE del teclado de la Commodore, esta tecla entra directamente al pin 6 de trigger del 556 y se conecta al al pin 4 NMI del 6510. El estar conectado a un circuito de timer permite que la salida del mismo sea calculada para ser de un intervalo preciso de duración.
  • El pin 21 IRQ de CIA 2 U2, con lo que cualquier interrupción causada por la CIA 2 causa un NMI, en particular las causadas por teclado o RS-232 I/O

Como ya estudiamos, el vector al cuál hace referencia una interrupción NMI es el $FFFA,$FFFB el mismo redirige a la dirección $FE43

Esta dirección contiene el siguiente programa, citado aquí por completitud, un resumen de su funcionamiento:

  • deshabilita las interrupciones tanto IRQ como NMI,
  • chequea si la interrupción vino del CIA 2 para ver si debe realizar alguna de sus rutinas de comunicación RS 232,
  • chequea que teclas han sido presionadas,
  • si fue la tecla restore, chequea la existencia de un cartucho y en caso de estar conectado el mismo es arrancado,
  • si se detectó  la tecla STOP realiza un restore de las variable de I/O de Basic, del sistema en general y de la pantalla,
  • finalizando con la activación de todas las interrupciones nuevamente tanto IRQ como NMI.

Este vector puede usarse para desactivar la secuencia de teclas RESTORE/STOP, el comando POKE 792,193 puede hacer esto modificando la rutina para que solo corra un return (RTI).

Interrupción enmascarable IRQ programas y funcionamiento

Al pin 3 IRQ del 6510 están conectados:

  • El pin 21 IRQ de la CIA 1 U1, con lo que cualquier interrupción generada por la CIA 1 causa un IRQ. En particular, al momento de inicializar la máquina el Timer B de la CIA 1 es configurado para emitir una interrupción cada 1/60 de segundo.
  • El pin 8 IRQ del chip de video VIC II 6567 U19. En particular el VIC II tiene un registro con 4 tipos diferentes de interrupciones:
  • Bit 0 cuando la posición del raster es la misma que la que se configuró en el raster register.
  • Bit 1 colisión entre Sprite y el resto de los datos en el display.
  • Bit 2 colisión entre dos Sprites.
  • Bit 3 cuando se detecta una transición a negativo por el light pen.

Al ejecutar una IRQ el siguiente código es ejecutado por el 6510 que fue señalado por el vector $FFFE,$FFFF conteniendo la dirección $FF48.

Este código realiza las siguientes funciones:

  • preserva el acumulador, y los registros X e Y en el stack,
  • como este servicio también responde al comando BRK que es una INT por software chequea el status register para ver si el bit de B (Break) esta prendido dependiendo del estado de este flag salta a dos programas distintos,
  • Opción 1 para atender a un BRK en $0316-$0317 (el mismo también se ejecuta al presionar STOP y RESTORE simultáneamente)
  • Opción 2 si fue una INT corriendo entonces $0314-$0315. Este último mantiene el Jiffy Clock o reloj de sistema, escanea el teclado, hace que parpadee el cursor, mantiene el interlock del datasette, entre otras funciones.

A continuación podemos ver el código que realiza estas rutinas

                           

.,EA86 40       RTI                    RTI             ;EXIT FROM IRQ ROUTINES

Estudio visual

Para poder estudiar visualmente cómo funcionan las interrupciones NMI e IRQ les dejo esta video que complementa al artículo

Interrupciones en el procesador del Commodore 64, Apple II y Atari 2600 – 6502 vs 6510 Episodio 12

Referencias

A continuación les dejo algunos links donde profundizar el tema:

WEBSITE

Aqui el sitio de OsoLabs con todos los videos y artículos

OsoLabs.tech 

VIDEOS

Video de la serie 6502 vs 6510 Parte 12 – Interrupciones

Interrupciones en el procesador del Commodore 64, Apple II y Atari 2600 – 6502 vs 6510 Episodio 12

Video para profundizar el saber sobre la VIA

MOS 6522 Versatile Adaptive Interface VIA – 6502 vs 6510 parte 7

Video para profundizar el saber sobre la CIA

6526 CIA Complex Interface Adapter Programado – 6502 vs 6510 parte 8 

Aquí tiene acceso a toda la serie de videos:

6502 vs 6510 estudio detallado y comparación 

PAPERS

W65C02S 8–bit Microprocessor 

6510 MICROPROCESSOR WITH I/O 

CIA 6526 Datasheet 

MCS6522 VERSATILE INTERFACE ADAPTER

W65C22 (W65C22N and W65C22S) Versatile Interface Adapter (VIA) Datasheet 

ROM de Commodore 64 comentada 

Mapping the Commodore 64 

6502-Block-Diagram.pdf 

27c3: Reverse Engineering the MOS 6502 CPU (en) 

Y como siempre la serie de Ben Eater del 6502

Build a 6502 computer | Ben Eater 

Todos los ejemplos de código de los videos los pueden encontrar en:

https://github.com/osolabstech/6502_vs_6510

Autor: OsoLabs

Empecé con la Commodore de chico, sigo con la Commodore de grande. Programo para 6502, Arduino, Raspberry y algunos algoritmos de Bioinfomática, algo en C++, algo en Python, algo en Assembler. Fanático de la electrónica home brew desde breadboard a diseñar PCBs propios. Armé la 20c y OsoLabs https://www.osolabs.tech Mi GitHub para quien quiera sumarse a los proyectos y chusmear código: https://github.com/carlinhocr/ Desde Argentina al Mundo

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *