Continuamos este estudio comparativo del 6502 vs el 6510 tratando de descubrir cómo es que el procesador accede a las instrucciones en código máquina, las lee, interpreta y ejecuta. También vamos a ver como codear nuestra primera instrucción directamente usando resistencias y el bus de datos del procesador.
Les dejo el link al articulo anterior en la serie, y al final como siempre los links a los artículos de la misma.
Qué son las instrucciones en código máquina
Un procesador no utiliza internamente ninguno de los lenguajes de alto nivel que solemos utilizar como por ejemplo sería el Basic en la Commodore, ni siquiera usa Assembler. Internamente un procesador solo tiene esquemas de transistores que responden a distintas combinaciones de unos y ceros, lo que denominamos el código máquina.
Set de instrucciones del 6502 y el 6510
El código máquina de ambos procesadores posee las mismas instrucciones de assembler de 6500 con lo que los programas de uno funcionan perfectamente en el otro. La siguiente tabla muestra un resumen de todas las instrucciones del procesador.
Y en la siguiente tabla tenemos todas las instrucciones del 6502 y el 6510 codificadas por su representación en hexadecimal o binario.
Podemos encontrar en esta tabla, por ejemplo, una instrucción muy común como es la LDA # que carga el registro del acumulador con un número de 8 bits que indicamos a continuación del símbolo #. Esta instrucción se encuentra en la Fila A Columna 9 en nuestra tabla.
El procesador para reconocerla espera ver al momento de buscar una instrucción en el bus de datos los valores 10101001 (correspondientes al hexadecimal A9) en los pines de datos del D7 al D0 de nuestros procesadores. Correspondientes a los pines 26 al 33 para el 6502 y del 30 al 37 para el 6510.
De dónde lee las instrucciones el procesador
Cuando prendemos por primera vez un procesador 6502 o 6510, el procesador busca siempre en las mismas direcciones de memoria un lugar donde le digamos dónde se ubica la primera instrucción del programa que queremos ejecutar. En el caso de estos procesadores puede buscar en sólo 3 lugares dependiendo cómo se esté realizando la inicialización de los mismos.
$FFFA-$FFFB si se recibe una interrupción no enmascarable (pin /NMI)
$FFFC-$FFFD si se recibe un reset (pin /RESET)
$FFFE-$FFFF para una interrupción (pin /IRQ o instrucción de código máquina break $00)
La selección de cuál de estos vectores (que así se llaman las direcciones de memoria donde se espera haya otra dirección de memoria) se activa es básicamente que pin de estos tres recibe un LOW, teniendo precedencia, primero /RESET, luego /NMI y finalmente /IRQ o break en caso de recibirlo los 3 a la vez.
Recordemos que las direcciones son de 16 bits ya que el 6502 puede direccionar hasta 65532 direcciones (2 a la potencia 16) ya que la memoria contiene datos sólo de 8 bits vamos a necesitar dos posiciones de memoria para cargar la dirección de nuestro primer programa, por eso para reset utilizamos la posición $FFFC y $FFFD. El procesador sólo necesita la primera posición de memoria y sabe que tiene que ir a buscar la segunda dirección.
Estas posiciones se graban con la característica de ser little endian, lo que significa que guardamos el byte menos significativo primero. Por Ejemplo si queremos guardar la dirección $0E00 como la primera de nuestro programa deberemos guardarla en memoria como 00 0E
Por lo que si quisieramos guardar la posición de memoria $0E00 cuando nuestro procesador realice un reset debemos almacenar el low byte $00 en la posición de memoria $FFFC y el high byte $0E en la posición $FFFD.
Posición de Memoria | Contenido |
FFFC | 00 |
FFFD | 0E |
Nuestra primera Instrucción, la no instrucción NOP
Como primera instrucción vamos a utilizar NOP, esta es la instrucción de no operación, le dice al procesador que no realice nada durante 2 ciclos de reloj. Si la buscamos en nuestra tabla vamos a ver que está en la fila E y la columna A o sea $EA, agrego el símbolo pesos para indicar que se trata de un número en hexadecimal.
Este número hexa se corresponde con el binario 11101010 y el procesador va a estar en el ciclo de búsqueda de instrucción esperándolo en los puertos de datos de nuestro procesador. Por lo que deberemos codificar para cada pin del procesador 6502 y para el 6510 respectivamente:
6502 | 6510 |
Pin 33 D0 = 0 | Pin 37 D0 = 0 |
Pin 32 D1 = 1 | Pin 36 D1 = 1 |
Pin 31 D2 = 0 | Pin 35 D2 = 0 |
Pin 30 D3 = 1 | Pin 34 D3 = 1 |
Pin 29 D4 = 0 | Pin 33 D4 = 0 |
Pin 28 D5 = 1 | Pin 32 D5 = 1 |
Pin 27 D6 = 1 | Pin 31 D6 = 1 |
Pin 26 D7 = 1 | Pin 30 D7 = 1 |
Normalmente el procesador elegirá un address de memoria y el chip de memoria le entregará los datos con las instrucciones correspondientes y estos valores binarios. Por ejemplo el procesador puede ir al address $0E00 y un chip de memoria entregarle el valor de 8 bits $EA contenido en esa dirección, luego de lo cual el procesador interpretará esta instrucción y la ejecutará.
Cargando la instrucción para que la lea el procesador, con un truquito.
En nuestra primera aproximación a codificar el 6502 y el 6510 “a mano” lo que vamos a hacer es fijar el bus de datos con los valores de la instrucción EA, conectando los pines del bus de datos D7 a D0 de la siguiente forma.
Utilizando una resistencia de 1k conectaremos cada pin que en el diagrama tenga un cero a tierra o low y cada uno que tenga un uno a 5V o al canal high. El número parece al revés que EA = 11101010 debido a que el pin de más a la derecha es D7 (el más significativo) y el de más a la izquierda D0 (el menos significativo).
De esta forma siempre que el procesador quiera cargar una instrucción y pasa a modo de ejecución va a cargar nuestra instrucción EA y sólo esa instrucción ya que es lo único que va a estar en el bus de datos.
Y qué pasa entonces
El procesador va a bootear e ir a las posiciones $FFFC y cargará $EA ya que es lo único que tiene en el bus de datos y luego a la posición $FFFD y cargará $EA que es lo único que tiene en el bus de datos claro ya que esta hardcodeado con las resistencias.
Con esto cargará el Program Counter con la primera posición de nuestro programa ficticio que será $EAEA y buscará el código de la próxima instrucción a ejecutar que será $EA ya que es lo único que tenemos en el bus de datos. Esta ejecución durará dos ciclos de reloj y luego el program counter avanzará a $EAEB y volverá a leer el bus de datos en búsqueda de la próxima instrucción que seguirá siendo $EA y así continuará.
Un gran programa para poder enfocarnos en cómo funciona de la forma más básica la carga en binario de una instrucción en código máquina.
Cómo funciona en el Commodore 64
Al encender la Commodore 64 un circuito de reset entra en acción. El mismo está compuesto por un integrado de tipo timer 556 que es ni más ni menos que dos timer 555 en el mismo chip, con todos los conectores de un 555 en los pines de la izquierda y de otro 555 en los pines de la derecha.
A continuación tenemos una foto de parte del circuito en una Commodore 64 real y un esquema del circuito eléctrico que vamos a analizar.
Análisis del Circuito de Reset
El 556 (componente U20) está cableado en forma monoestable lo que significa que por cada vez que es activado a través de su pin de trigger envía un pulso a través de su pin de output. Este circuito es activado solo al encender la computadora y después no vuelve a funcionar en la Commodore. Al ser dos timers 555 vamos a mirar la parte derecha del mismo, los pines con el número 2 que son los que forman parte del circuito de reset.
Primero tenemos que estudiar la parte del circuito que activa al 556 a través de su pin de trigger, este pin se activa solo cuando el pin de trigger recibe un voltaje menor de ⅓ del voltaje total.
Al encender la Commodore la carga del capacitor c105 es cero con lo que el voltaje en el pin TRIG es menor a ⅓ del voltaje total y un pulso es emitido. El capacitor C105 se carga a través de una resistencia de 1 Mega Ohms hasta que llega a los 5 volts lentamente y el pin TRIG deja de tener un voltaje menor a ⅓ del voltaje total con lo que el estímulo para activarse deja de existir para no volver nunca a un valor low mientras dure la Commodore encendida.
.
Cuando el capacitor c24 se carga a ⅔ del voltaje total se activa el pin de Discharge y el 556 corta el pulso que estaba emitiendo.
Si queremos calcular aproximadamente cuanto dura el estímulo del pulso, podemos aplicar la formula para saber cuánto tarde un capacitor de 0.1 microfaradios en cargar a través de una resistencia de 1 Millón de Ohms
Constante de Tiempo = Resistencia en Ohms x Capacitancia en Faradios
=47000 ohms x 0.00001 faradios = 0,47 segundos
El tiempo para la carga total es de Constante de tiempo x 5 = 0,47 seg x 5 = 2,35 segundos
Pero no necesitamos que se llene completamente el capacitor en 5 Volts solo hasta que llegue a 2.33 volts o dos tercios del valor que es aproximadamente dos quintos del tiempo de carga total con lo que el pulso de output se mantiene por ⅖ * 2.35 seg = 0,94 segundos.
Pero esta salida de pin de Output es HIGH y cuando el impulso es disparado por el 556 el mismo pasa por un inversor para salir LOW y conectado a la línea de RESET, que está conectada al 6510 en su pin /RESET ocasionando un reset del procesador.
Esta salida del inversor está conectado a una resistencia a 5Volts también llamada resistencia pull-up que mantiene el valor de la salida en HIGH evitando el reset siempre y cuando el inversor no reciba un nuevo output que sabemos no pasará por que el capacitor c105 mantiene los valores arriba de ⅓ del voltaje total.
Que ocasiona un pulso de reset en el 6510 y sus periféricos
Cualquier chip (sid, cia, etc), bus (IEC, etc) o dispositivo conectado a un bus (disketteras) que estén conectados a esta línea de reset comenzarán sus propias inicializaciones.
Al haber ejecutado un reset el procesador 6510 buscará la dirección de memoria para su primera instrucción en las direcciones $FFFC y $FFFD low y high byte respectivamente.
Estudiando el mapa de memoria del Commodore 64 veremos que eso nos lleva a la dirección $FCE2 la primera línea de la rutina de Power On del Commodore 64, como se referencia en libro Mapping The Commodore 64
La siguiente rutina de Kernel es la que se ejecuta.
Esta rutina activa el flag de deshabilitar interrupciones, programa el stack pointer para estar vacio con la dirección $01FF, deshabilita el modo decimal del procesador, busca si existe un cartucho en el puerto de expansión y si se encuentra le da al programa del cartucho el control de la Commodore, si no continúa con las rutinas de inicialización del kernel, inicializa los chips SID y CIA, inicializa la RAM, inicializa el VIC, limpia el flag de deshabilitar interrupciones y comienza el intérprete BASIC.
Con esta clásica pantalla azul concluye nuestro pequeño resumen de como arranca nuestro Commodore 64.
Codificando la primera instrucción visualmente
Para poder estudiar visualmente como codificar la instrucción EA con resistencias y ver cómo busca las instrucciones el 6502 y el 6510 les dejo esta video que complementa al artículo.
6502 vs 6510 instrucción NOP (EA) codificada – Parte 3
Artículos en la serie C64 a Fondo
A continuación el link al próximo artículo en la serie
Parte 4 – Primer Programa desde EEPROM
y aquí los links a los artículos anteriores
Referencias
A continuación les dejo algunos links donde profundizar el tema:
VIDEOS
Video de la serie 6502 vs 6510 Parte 3 – instrucción NOP (EA) codificada
6502 vs 6510 instrucción NOP (EA) codificada – Parte 3
Aquí tiene acceso a toda la serie:
6502 vs 6510 estudio detallado y comparación
PAPERS
Load and Run from 6502 ASM (1/2) | C64 OS
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: