The 20c – Episodio 4 – ROM, Read Only Memory

Introducción 

En este episodio vamos a conocer al Chip ROM o Read Only Memory el cual tiene la funcionalidad de darnos una memoria de sólo lectura. Esta memoria nos va a servir para poder almacenar todas las instrucciones de nuestros programas.

Para usar este chip diseñamos una placa Dual que va a poder correr tanto un chip ROM como uno RAM que comparten el mismo pinout, en este episodio nos vamos a enfocar en la ROM.

Vamos a estudiar el pinout del chip, como funciona, como poder grabarlo usando un programador de EEPROM y vamos a realizar experimentos que nos dejen almacenar y ejecutar nuestro primer programa.

Como siempre vamos a explicar como crear la placa desde cero y como funciona la misma.

Descripción general de la placa ROM

Nuestra Placa RAM/ROM, cuando la configuramos en modo ROM, sirve para almacenar los programas que vamos a ejecutar en nuestra 20c. 

En nuestro caso el chip a utilizar es el AT28C256 de ATMEL. Este chip posee 32768 registros de 8 bits cada uno, por lo que podemos tener hasta 32k bytes de información en este tipo de memorias. El 256K del nombre, se refiere a los 256 Kbits individuales que posee la placa los que equivalen a 32 Kbytes (256/8=32). 

Para grabar este chip con instrucciones utilizaremos un grabador de memorias EEPROM.

Este chip viene en formato DIP (Dual Inline Pins).

Descripción del Pin Out del chip AT28C256 

Este chip presenta 28 pines con el siguiente funcionamiento:


A14 – A0: Estos pines nos permiten seleccionar qué registro de ocho bits queremos acceder dentro de nuestra memoria, al ser 15 pines podemos direccionar 2ˆ15 = 32768 registros de 8 bits. Estos pines se conectan al bus de direccionamiento o ADDRESS BUS .

I/O 0 a I/O 7: Los pines de input/ouput o I/O es donde vamos a ver el contenido de cada registro previamente seleccionado para leer la memoria, o donde vamos a enviar los datos que tenemos para escribir la memoria. Estos pines se conectan al bus de datos o DATA BUS .

VCC: En este pin es donde el chip espera una alimentación de +5Volts.

GND: Este es el pin de referencia a tierra del chip.

/WE: El pin de Write Enable al recibir una señal de low o 0 Volts permite grabar en los registros de la memoria. Como la estamos utilizando como una ROM conectamos este pin directamente a +5 Volts para que sea de sólo lectura. La barra / significa que este pin es active low con lo cual espera 0 Volts para activarse.

/OE: El pin de output enable conecta o desconecta los pines de I/O del bus de datos. Si el pin está en +5 Volts la memoria se desconecta del bus de datos poniendo sus pines de datos en un estado de alta impedancia. La barra / significa que este pin es active low con lo cual espera 0 Volts para activarse.

/CE: El pin de chip enable conecta o desconecta los pines del chip para una lectura o escritura trabajando en conjunto con /OE y /WE. Es active low con lo cual espera 0 Volts para activarse.

Mapa de Memoria

El 6502 puede direccionar hasta 65536 registros de 8 bits cada uno o 64K como nos gusta decir. Esto nos dejaría utilizar hasta 2 chips EEPROM de 32k cada uno.

Las direcciones que maneja el procesador van desde la $0000 hasta la $FFFF, siempre en hexadecimal como indicamos al anteponer el signo pesos. Si quisiéramos repartir estas direcciones entre 2 chips podríamos asignar de $0000 a $7FFF para el primero y de $8000 a $FFFF para el segundo.

Nuestro chip posee 32kb de espacio pero vamos a tener que decidir con qué direcciones de memoria vamos a querer accederlo, esto se denomina mapa de memoria.

El mapa de memoria indica para cada chip y periférico de nuestra 20c qué direcciones del ADDRESS BUS van a activarlo para acceder a sus registros, en el caso de chips con registros para configurarlos o en el caso del chips de memorias ROM y RAM para leer o modificar su contenido.

Vamos a decidir colocar nuestro chip ROM en las direcciones de la $8000 a $FFFF principalmente por que ahi vamos a tener 3 posiciones de memoria o vectores muy importantes, los dos de interrupciones NMI e INT y el de RESET que como recordamos es donde el chip 20c busca la dirección donde va a estar el primer programa a ejecutar cuando prendemos el mismo.

Este mapa de memoria es el recomendado originariamente en el manual de Hardware y el en de Programación de MOS Technologies.

El siguiente es el mapa de memoria de la 20c

En este mapa podemos ver donde tenemos el chip ROM y algunos otros chips extra que vamos a estudiar en futuros episodios.

Cómo acceder al Chip

El chip no se encuentra todo el tiempo activo ya que si no tendría siempre el DATA BUS ocupado. El CPU ordena a los chips de la computadora y le indica al chip que quiere leer sus datos a través de los pines de ChipSelect /CS y OutputEnable /OE.

Aquí es donde entra en juego el mapa de memoria, vamos a activar los pines de /CS y /OE cuando en el ADDRESS BUS se escriba una dirección de las que definimos como parte del chip ROM. Este chip por definición va a ser de sólo lectura.

Lectura

Para realizar una lectura deberemos primero poner en el bus de direccionamiento el address de la memoria que queremos acceder y acto seguido poner los pines:

/WE en +5 Volts (High)

/OE en 0 Volts (Low)

/CE en 0 Volts (Low)

En el bus de Datos tendremos la información asociada a la dirección que pusimos en el address bus. El /WE o el /CE deberá ser un pulso, cuando indicamos que una señal debe ser un pulso se debe a que el chip reacciona, no al valor específico en volts, sino al cambio de voltaje, en el caso de /CE de +5Volts a 0V también llamado Falling Edge.

Escritura

Para realizar una escritura deberemos primero poner en el bus de ADDRESS BUS la dirección de la memoria que queremos escribir, luego en el bus de datos o DATA BUS la información a grabar y acto seguido poner los pines:

/WE en 0 Volts (Low)

/OE en +5 Volts (High)

/CE en 0 Volts (Low)

Recordemos que al tratarse de una ROM no vamos a realizar escrituras a la misma, esto lo vamos a lograr conectando el chip /OE de output enable siempre a +0v o Ground.

Cómo seleccionamos la memoria desde el procesador

Nuestro chip está en las direcciones desde $8000 a $FFFF. Vamos a ver como se ven estas direcciones en binario para observar si podemos sacar algún patrón útil.

Espacio de DireccionamientoHexaBinario
Superior$80001000 0000 0000 0000
Superior$80011000 0000 0000 0001
Superior………………………………………
Superior$FFFE1111 1111 1111 1110
Superior$FFFF1111 1111 1111 1111

Podemos observar que el primer bit en Binario es uno. Para direccionar todos los registros de la eeprom necesitamos 15 pines del A0 al A14 con lo que nos sobra el pin A15. 

Para poner en modo de lectura solamente al chip podríamos entonces conectar en forma permanente los pines:

/WE en +5 Volts (High)

/OE en 0 Volts (Low)

También podemos conectar el pin /WE al pin de R/W del procesador 6502 ya que el mismo emitirá un pulso de +5V al realizar una lectura, pero por si se equivoca y envia una escritura no queremos que esto afecte a nuestra memoria así que no lo hacemos.

El pin A15 queda con un valor de 1 o +5 Volts (High) no podríamos conectarlo directamente al pin de /CE ya que no se activaría por ser Active Low el pin. Lo que deberíamos hacer es invertir el valor de 1 a 0 y propongo hacerlo con una compuerta NAND.

Una compuerta NAND tiene la siguiente tabla de verdad que es la opuesta a una compuerta AND.

Input AInput BResultado
LOWLOWHIGH
LOWHIGHHIGH
HIGHLOWHIGH
HIGHHIGHLOW

Con lo que si conectamos en la misma compuerta NAND como input A la salida en +5 Volts (HIGH) del pin A15 y como input B también la salida en +5 Volts (HIGH) del pin A15 el resultado será un 0 Volts (LOW) que permitirá activar el pin /CE de chip enable.


Para esto podemos utilizar un clásico chip como ser el 74HC00 que posee cuatro compuertas NAND de dos inputs cada una.


Veremos cómo realizar la conexión a nuestra placa en la sección Guía de Conexión.

Programando en Assembler

Para poder programar nuestro chip 6502 con facilidad vamos a utilizar un programa Assembler llamado VASM. Este ensamblador nos va a permitir compilar a binario nuestro programa. El link para descargarlo se encuentra en las referencias.

Programa

Empecemos con un programa assembler sencillo que emula nuestros experimentos anteriores con resistencias para codificar la instrucción de no operación NOP cuyo opcode es 0xEA.

Vamos a explicar línea por línea que hace nuestro programa:

Líneas 1 y 2 comentarios

Línea 3 .org 8000 

Indica que queremos comenzar nuestro programa a partir de la dirección $8000 justo donde comienza nuestra ROM. Esto va a hacer que el compilador cuando traduzca nuestro programa a los OPCODEs de cada operación los situe a partir de la dirección $8000.

Línea 4 vacia

Línea 5 RESET 

Esto se denomina un LABEL nos sirve para poder indicar una parte del código donde vamos a querer llegar sin tener que saber exactamente que dirección hexadecimal posee en la memoria, el ensamblador se va a encargar de poner la dirección Correcta durante el ensamblaje (el ensamblador que ensamble un buen ensamblaje, buen ensamblador será).

Este importante esta LABEL en particular ya que el 6502 automáticamente busca en la dirección $FFFC y $FFFD la dirección donde está la primera instrucción a ejecutar y nosotros le vamos a pedir que sea la dirección donde esta el LABEL RESET.

Línea 6 comentario

Línea 7 a 16 NOP

Comienzan nuestras instrucciones con la instrucción NOP de no operación, el 6502 lo va a leer y va a pasar dos ciclos de reloj sin hacer nada. En nuestro programa compilado va a poner 10 bytes EA consecutivos.

Línea 17 vacia

Línea 18 comentario

Línea 19 .org FFFA

Aquí le decimos al compilador que a partir de esta dirección ponga las instrucciones siguientes

Línea 20 RESET

Entonces como dato la dirección del label RESET, como escribe LowByte, HighByte por se rel procesador 6502 Little Endian va a ir en la dirección $FFFA $00 y en la $FFFB $80. Esta dirección carga el vector de interrupciones NMI.

Línea 21 .org FFFC

Aquí le decimos al compilador que a partir de esta dirección ponga las instrucciones siguientes

Línea 22 RESET

Entonces como dato la dirección del label RESET, como escribe LowByte, HighByte por se rel procesador 6502 Little Endian va a ir en la dirección $FFFC $00 y en la $FFFD $80. Esta dirección carga el vector de interrupciones de la operación RESET, luego de un reset del procesador va a cargar como primera instrucción la que aquí se encuentre.

Línea 23 comentario

Línea 24 .org FFFE

Aquí le decimos al compilador que a partir de esta dirección ponga las instrucciones siguientes

Línea 25 RESET

Entonces como dato la dirección del label RESET, como escribe LowByte, HighByte por se rel procesador 6502 Little Endian va a ir en la dirección $FFFE $00 y en la $FFFF $80. Esta dirección carga el vector de interrupciones IRQ.

Compilando

Ahora que ya tenemos nuestro código fuente, vamos a compilar nuestro código assembler  a código de máquina usando vasm.


Aquí le doy varias opciones al compilador, vamos a verlas

Vasm6502_oldstyle, esta opción le dice al ensamblador que utilice el módulo de sintaxis Oldstyle, esto va a definir distintas particularidades de como escribir los LABELs y otros detalles

-Fbin, vamos a señalar como módulo de Output el simply binary output module, este módulo va a escribir la salida de todas instrucciones que tenemos en nuestro código fuente como un archivo binario. Los espacios vacíos entre secciones .org que no llenamos con instrucciones van a tener el byte $00

-dotdir, que acepte en nuestro código direcciones especiales al ensamblador como ser por ejemplo .org

ep4_firstprogram.asm, el nombre de nuestro archivo con el código fuente

-o, a continuación indicaremos el archivo donde poner el código binario ya ensamblado

ep4_firstprogram.bin, el nombre de nuestro programa ensamblado en binario

Análisis del código máquina

Ahora utilizando el programa hexdump examinemos nuestro código máquina y tratemos de interpretar donde están las instrucciones anteriores.

A las direcciones que vemos sumemosle $8000 por como muestra el archivo hexdump, con lo que donde dice 00000000 miremos 000008000. ¿Por qué es así? Por que la ROM sólo tiene 32kb y nosotros lo ponemos en el mapa de memoria de nuestro 6502 a partir de la dirección $8000, pero no existe esta dirección dentro de la ROM ya que 32kb van desde $0000 a $7FFF. Hexdump solo muestra la realidad del interior del archivo sin saber donde vamos a mapear nosotros.

Línea 00000000 aquí vemos 10 veces el byte EA que corresponde al código de operación de NOP

Línea 00000010 todos ceros, recuerden como -Fbin rellenaba los espacios que no definimos

Línea 00007ff0 aqui finalmente vemos los últimos 6 bytes como 00 80 00 80 00 80 que corresponde a las direcciones LOW y HIGH para el LABEL de reset que está definido en la dirección $8000 para los 3 vectores de 6502 NMI, RESET e IRQ.

Ahora que tenemos nuestro código máquina vamos a grabarlo en el chip de EEPROM 

Cómo grabar el Chip con un grabador de EEPROM

Para escribir las instrucciones de nuestro chip vamos a usar un grabador externo que nos va a permitir completar cada casillero de memoria con las instrucciones que queramos

Ya que tenemos nuestro primer programa como un archivo binario de 32768 bytes, solo nos resta grabar el mismo en una EEPROM y esto se realiza con un grabador de eeproms. En este ejemplo vamos a usar un TL866 II Plus o el T48.

Este programador de eeprom es muy sencillo de utilizar a través del programa minipro, al mismo se le indica qué tipo de chip eeprom vamos a grabar y que archivo queremos grabar. 

Antes de comenzar a grabar conectamos el grabador a nuestra computadora, e insertamos el chip eeprom en el zócalo zif 

Vamos a utilizar el programa minipro para poder hacer la escritura del chip, donde conseguir el mismo está incluido en las referencias de este mismo episodio.

Para grabar un archivo binario con el nombre por ejemplo ep4_firstprogram.bin ejecutaremos el siguiente comando.

$ minipro -p AT28C256 -w ep4_firstprogram.bin

La opción -p es para especificar el modelo de la memoria que vamos a grabar en este caso una AT28C256 de ATMEL.

La opción -w para especificar que queremos hacer una escritura y a continuación el nombre del archivo a grabar.

Y con esto tenemos nuestro primer programa grabado en ROM, en los experimentos vamos a ver que ejecuta nuestra computadora con este primer programa

Circuito de la Placa ROM de la 20c

El siguiente gráfico muestra el circuito que explica el funcionamiento de la Placa ROM de la 20c.

El circuito posee los siguientes elementos:

SOCKET RAMROM

El cual está representado físicamente como un socket DIP-28 de 28 pines, a cada uno de estos pines se le conectó un Netlist representando cada una de las funciones de estos pines por ejemplo el netlist A0 representa el pin A0 que estará conectado a todos los elementos que tenga que estar conectados con la línea o trace del address 0. 

Tiene conectado los netlists de A0 a A14, D0 a D7 y todos los netlist de /RW (señal de Write Enable), OE y CS. Para darle energía esta conectado a los planos de +5b y GROUND.

HEADER ADDRESS

Este es un header hembra de 16 pines el cuál se usa para conectar el address bus, el mismo tiene conectados 16 netlist (o conexiones entre varios pines que tienen la misma información). Estos netlist corresponden a los pines A0 hasta A15. 

Tenemos dos de estos conectores para poder tener la información en la parte superior e inferior de la placa y realizar interconexiones con otras placas.

HEADER DATA

Este es un header hembra de 8 pines el cuál se usa para conectar el data bus, el mismo tiene conectados 8 netlist (o conexiones entre varios pines que tienen la misma información). Estos netlist corresponden a los pines D0 hasta D7.

Tenemos dos de estos conectores para poder tener la información en la parte superior e inferior de la placa y realizar interconexiones con otras placas.

HEADER EXPANSION

Este es un header hembra de 16 pines el cuál se usa para conectar el expansion bus, el mismo tiene conectados 16 netlist (o conexiones entre varios pines que tienen la misma información). Estos netlist corresponden a algunos netlist conectados directamente a pines de RAMROM como ser:

RW, OE, CS

o señales genéricas que pueden transmitir información a otras placas como ser:

RST, CLOCK, RW, E5 hasta E15

Tenemos dos de estos conectores para poder tener la información en la parte superior e inferior de la placa y realizar interconexiones con otras placas.

HEADER POWER y GROUND

Este header posee dos pines y están conectados a las capas de 5V y de GROUND respectivamente. Tenemos cuatro de estos conectores, uno en cada esquina de la placa.

CAPACITOR

Tenemos un capacitor de 0.1 microFaradios para que cuando el CPU arranca pueda disponer del pico extra de energía que necesita.

AGUJEROS para TORNILLOS

Para poder fijar nuestra placa a distintos lugares la misma cuenta con 6 agujeros para tornillos de 3mm o M3.

SWITCH RAMROM SW1

Este switch no posee ninguna característica eléctrica y no está conectado a ning;un circuito, como nuestra placa puede funcionar tanto como ROM como RAM vamos a correr el switch y dejarlo en la posición en que vamos a usar la placa.

NETLIST

Los componentes están conectados entre sí a través de un objeto llamado netlist el cual nos permite tener diagramas más limpios.

Este funciona de forma tal que todo lo que tenga el mismo nombre pertenece al mismo netlist y deberá estar conectado junto cuando hagamos el PCB (con traces). 

En nuestro ejemplo todas las conexiones A0 estarán conectadas por traces en el PCB

PCB de la Placa ROM de la 20c

El siguiente PCB explica cómo se ubican los elementos y traces para conectar nuestro circuito. El PCB que diseñamos está hecho con 4 capas de cobre.

TOP

En la capa llamada TOP que es la que podríamos ver como arriba y de frente en nuestra PCB vamos a tener algunas las conexiones de nuestros netlist a través de traces, los cuáles son finas líneas de cobre que conectan los mismos como si fueran cables y los todos los componentes ya sea sockets, capacitores y headers.

LAYER 5V

En la capa +5v conectaremos las conexiones de power. Al usar estas conexiones en una capa separada nos ahorramos muchos centímetros de traces que deberían conectarse a pines con los voltajes de 5V podemos conectar directamente el pin de cada componente a esta capa a través de un agujero llamado VIA.


LAYER GROUND

En la capa GROUND conectamos las conexiones de ground o masa. Al de tener una capa entera para GROUND es poder aislar el ruido eléctrico de las conexiones entre componentes.

BOTTOM

Por último tenemos la capa de BOTTOM donde se sueldan nuestros componentes y tenemos conexiones adicionales.


Modelado 3D

Cuando terminamos de realizar nuestra Placa debemos proceder al modelado 3D de la misma para observar cómo queda y si nos gusta el lugar donde pusimos cada componente

.

Generación de archivos fuent

Para poder mandar a fabricar nuestro PCB deberemos contar con al menos 3 archivos:

  1. Archivo .gerber donde le indicamos al fabricante como armar nuestra placa
  2. Archivo BOM o Bill of materials donde se encuentran todos los componentes que forman nuestra placa con el código de producto del fabricante.
  3. Archivo Pick and Place donde le indicamos al fabricante cómo ubicar los componentes en nuestra placa PCB.

lista de archivos

Estos archivos los van a encontrar previamente generados en el github de Osolabs. También incluimos archivos para poder editar el circuito con el software EasyEDA, Kickcad o Altium Designer.

Con estos tres archivos vamos a poder fabricar nuestra placa

Gerber_v03_RAM_ROM_4Plane_2026-05-26.zip Este archivo contiene todos los planos para poder generar nuestra placa PCB.

BOM_v03_RAM_ROM_4Plane_v03_RAM_ROM_4Plane_2026-05-26.xlsx Contiene todos los componentes de nuestra placa y el código del fabricante que identifica a ese componente.

PickAndPlace_v03_RAM_ROM_4Plane_2026-05-26.xlsx

Indica en qué parte del PCB va cada componente y en qué capa debe ubicarse.

Los siguientes archivos se proveen para poder editar el circuito 

3DModel_ramrom_front.png

Es una imágen con la parte de atrás de nuestra placa modelada en 3D

3DModel_ramrom_back.png

Es una imágen con la parte de adelante de nuestra placa modelada en 3D

Altium_Circuit_1_v03_RAM_ROM_4Plane.schdoc

Contiene el diseño del circuito editable con el programa Altium Designer.

Altium_PCB_v03_RAM_ROM_4Plane.pcbdoc

Contiene el diseño del PCB editable con el programa Altium Designer.

PCB_v03_RAM_ROM_4Plane_2026-05-26.pdf

Es un PDF con un dibujo de cada capa del PCB

ProDoc_v03_RAM_ROM_4Plane_2026-05-26.epro

Archivo para EasyEDAPro con el circuito y el PCB el cual puede editarse.

SCH_v03_RAM_ROM_4Plane_2026-05-26.pdf

Es un PDF con los circuitos lógicos de la placa

Preparación de las Placa ROM

Al recibir nuestra placa de la fábrica y antes de comenzar a utilizar la misma y al momento que la sacamos de la caja, hay algunas tareas que deberemos hacer para poder hacer uso al máximo de la misma:

  1. Remover los bordes de manipulación de la placa
  1. Limpiar placa con alcohol isopropílico
  1. OPCIONAL – Poner un zócalo ZIF de 28 pines en el socket de la placa
  1. Poner el chip AT28C256 en el socket
  1. Ajustar a mano las patas del chip para que calcen el socket
  2. Alinear pin 1 con el pin 1 en el socket
  3. Primero insertar un lado del chip, poniendo el mismo en diagonal y luego el otro lado
  4. Verificar que ninguna patita quedó doblada
  5. Hacer presión y ver que todas están bien ajustadas en el socket
  1. Poner Tornillos de soporte de la placa para que esté levantada por encima de la superficie o instalarla, en nuestro caso usamos la base impresa en 3D de la 20c que vamos a aprender a crearla e imprimirla en un próximo Episodio
  1. Configurar switch SW1 en modo ROM para acordarnos que función cumple la placa.

Guía de Conexión de la placa ROM de la 20c

Vamos a explicar como conectar los distintos componentes de la placa para dejar esta lista para funcionar en una maqueta. Tené esta sección como referencia para cuando ensambles toda la 20c.

La memoría ROM queremos activarla para que funcione en las direcciones $8000 a $FFFF. Para esto vamos a necesitar tener el pin  A15 en uno como ya hemos visto.

A15A14A13A12address
ROM1XXX8000-FFFF

La memoria ROM tiene dos pines para activarse /OE y / CS ambos tienen que estar en cero para que la misma esté activada.

/OE memoria ROM

El Pin /OE vamos a conectarlo directamente a la señal de GROUND para que esté siempre activo .

R/W memoria ROM

El Pin R/W vamos a conectarlo directamente a +5v para que la memoria sea de sólo lectura siempre .

/CS memoria ROM

El pin de chip select vamos a seleccionarlo con el pin A15. Al pin A15 vamos a combinarlo consigo mismo en una compuerta NAND de forma tal que cuando valga un 1 lógico o +5v la  compuerta NAND dará un 0 lógico o +0v. Como ya estudiamos por el comportamiento de la compuerta NAND

Esa salida de la compuerta NAND vamos a conectarla a través del EXPANSION BUS con el pin /CS

Vamos a utilizar al el chip 74HC00, usando los pines 2A, 2B y la salida 2Y utilizando un breadboard

  1. Conectar el chip 74hc00 a un breadboard montando el lado izquierdo del chip en la parte por arriba de la canaleta del breadboard y la parte derecha en la parte inferior a la canaleta del mismo. Esto hará que respetemos la orientación del esquema que incluimos con el PIN 1 abajo a la izquierda.
  1. Conectar el pin de VCC al carril de +5v o rojo del breadboard
  1. Conectar el pin de GND o ground +0v al carril azul del breadboard.
  1. Conectar las canaletas superiores e inferiores del breadboard de +5v y 0v o ground entre sí
  1. Para poder hacer el efecto en la compuerta NAND conectaremos el pin A15 del bus de direccionamiento al pin 2B del 74HC00 y desde el mismo breadboard conectaremos el pin 2B al 2A.
  1. Conectamos la Salida 2Y al pin /CS del bus de expansión
  1. Finalmente para que el chip 74HC00 funcione no podemos dejar ninguno de sus inputs flotantes por lo que los conectamos todos a +5v. Estos son los pines 1A, 1B, 2A, 2B, 4A, 4B.

Conexión del resto de la placas

Para poder realizar nuestro experimentos tenemos que configurar el CLOCK y la CPU 6502 de la 20c asi cómo darle power en +5v.

  1. Conectamos un módulo de power a nuestro breadboard seleccionado el carril en +5v, tener en cuenta de poner el pin del módulo que dice + en el carril rojo de +5v.
  1. Conectamos el carril rojo del breadboard al pin de +5v de la placa de RAM/ROM.
  1. Colocamos al costado de la placa de RAM/ROM la placa de CLOCK y conectamos los pines de power +5v y GROUND entre las dos placas
  1. Conectamos la salida del reloj paso a paso de la placa de CLOCK al pin CLOCK de la placa de RAM/ROM pero en el pin header inferior, el que está más cerca del breadboard
  1. Colocamos la placa de CPU encima de placa de RAM/ROM y conectamos todos los headers de ADDRESS BUS y de DATA BUS. También vamos a conectar entre sí el pin de CLOCK y los pines de +5v y GROUND
  1. Instalamos la placas de DUINO PROTOCOL ANALYZER junto a su Arduino Mega en el header superior de la placa de CPU 6502
  1. Por último observamos que así nos queden todas las conexiones

Experimento 1 – Grabando solo NOPs

Vamos a reproducir el experimento que usábamos grabando la instrucción de no operación NOP, cuyo opcode es 0xEA como vinimos haciendo hasta acá y estudiaremos cómo se comporta la 20c usando la placa de DUINO PROTOCOL ANALYZER.

Vamos a usar el programa que creamos en la sección sobre código máquina. Este programa usa principalmente la instrucción NOP:

Para el detalle línea por línea de lo que hace el programa ir a la sección Programando en Assembler


Vamos a compilarlo con vasm.

Y ahora colocamos la eeprom en el programador


Corremos el comando minipro y lo grabamos


Y ahora vamos a estudiar la salida del procesador usando el DUINO PROTOCOL ANALYZER, utilizando el script 001_Arduino_DUINO_PA que ya vimos en el Episodio 3 sobre el DUINO PA y analizaremos el output de nuestro experimento.

Reset seguido de lectura de instrucciones NOP

  1. Conectamos el pin CLK del header CLOCK_555_OUT
  1. Seleccionemos el switch para el reloj paso a paso. 
  1. Presionemos el botón de reset de nuestro CPU y dejemoslo presionado.
  1. Presionar 3 veces el botón de STEP BUTTON de nuestra placa de CLOCK
  1. Soltar el botón de RESET de nuestro CPU
  1. Continuemos presionando el botón de STEP BUTTON de nuestra placa de CLOCK y veremos que cada vez que lo hacemos avanza un ciclo en  nuestro CPU, algunas instrucciones ocupan un ciclo de reloj y otras más
  1. Observamos en detalle los datos que nos da nuestro DUINO PROTOCOL ANALYZER a través del Arduino Mega.

El procesador va a encender y buscar en las posiciones $FFFC y $FFFD la dirección de la primera instrucción a ejecutar ordenados como Low Byte y High Byte. 

$FFFC contiene $00 = %0000 0000 

$FFFD contiene $80 = %1000 0000

En la misma va a encontrar los bytes $00 y $80 al utilizar la notación little endian la dirección que va a cargar el cpu 6502 en su registro de program counter va a ser $8000

Si lo ordenamos por los pines del address bus veríamos:

El Pin A A15 es un 1 que a través de la compuerta NAND se transforma en 0 equivalente a 0 Volts o Low, y como está conectado al  pin /CE nuestra EEPROM se activará.

Con esto cargará el  Program Counter con la primera posición de nuestro programa ficticio que será $8000 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. 

La próxima instrucción leída del data bus es la instrucción EA la cual significa la no operación NOP, sabemos que es un código de operación porque el flag S de SYNC está activado en nuestro bus de EXPANSION BUS significando un ciclo de OP-FETCH. 

Esta ejecución durará dos ciclos de reloj y luego el program counter avanzará a 8001 y volverá a leer el bus de datos en búsqueda de la próxima instrucción que seguirá siendo EA y así continuará.

El ciclo de ejecución de la primera instrucción es la siguiente:

  1. 8000 lee del databus el opcode hay +5V en el pin de SYNC lo interpreta como una instrucción Incrementa el program counter.
  2. Ejecuta la instrucción EA o sea NOP de no operación (ciclo 1 de 2)  y pone 8001 en el address bus
  3. Termina de ejecutar la instrucción NOP (ciclo 2 de 2) y trae la próxima instrucción de la dirección 8001 que sigue en el address bus. Incrementa el program counter.

Experimento 2 – Ejecutando un Loop 

En este experimento estudiaremos un simple loop y estudiaremos cómo se comporta la 20c usando la placa de DUINO PROTOCOL ANALYZER.

En este nuevo experimento vamos a usar principalmente dos instrucciones:

Creemos un segundo programa entonces donde vamos a ejecutar la instrucción de carga al acumulador LDA cargando el número 42 en hexadecimal $42. Vamos a usar la instrucción jump JMP para volver a ejecutar la instrucción de carga LDA en un bucle infinito y observar qué sucede.

Veamos línea a línea que hace nuestro programa.

Líneas 1 y 2 comentarios

Línea 3 .org 8000 

Indica que queremos comenzar nuestro programa a partir de la dirección $8000 justo donde comienza nuestra ROM. Esto va a hacer que el compilador cuando traduzca nuestro programa a los OPCODEs de cada operación los situe a partir de la dirección $8000.

Línea 4 vacia

Línea 5 RESET 

Esto se denomina un LABEL nos sirve para poder indicar una parte del código donde vamos a querer llegar sin tener que saber exactamente que dirección hexadecimal posee en la memoria, el ensamblador se va a encargar de poner la dirección Correcta durante el ensamblaje (el ensamblador que ensamble un buen ensamblaje, buen ensamblador será).

Es importante esta LABEL en particular ya que el 6502 automáticamente busca en la dirección $FFFC y $FFFD la dirección donde está la primera instrucción a ejecutar y nosotros le vamos a pedir que sea la dirección donde esta el LABEL RESET.

Línea 6 comentario

Línea 7  LDA #$42

Comienzan nuestras instrucciones con la instrucción LDA #$42 esta instrucción carga en el acumulador el valor 42 en hexadecimal, es un instrucción de modo inmediato ya que estamos cargando un número literal no una posición de memoria y su OPCODE es A9. 

Línea 8 JMP RESET

Aquí ejecutamos la instrucción JMP o Jump al label RESET, esto hara que nuestro program counter tome el valor del label RESET en este caso $8000 y volvamos a correr la instrucción LDA #$42 en un bucle infinito. El OPCODE de la instrucción JMP es $4C. 

Línea 9 vacia

Línea 10 comentario

Línea 11 .org FFFA

Aquí le decimos al compilador que a partir de esta dirección ponga las instrucciones siguientes

Línea 12 RESET

Entonces como dato la dirección del label RESET, como escribe LowByte, HighByte por se rel procesador 6502 Little Endian va a ir en la dirección $FFFA $00 y en la $FFFB $80. Esta dirección carga el vector de interrupciones NMI.

Línea 13 .org FFFC

Aquí le decimos al compilador que a partir de esta dirección ponga las instrucciones siguientes

Línea 14 RESET

Entonces como dato la dirección del label RESET, como escribe LowByte, HighByte por se rel procesador 6502 Little Endian va a ir en la dirección $FFFC $00 y en la $FFFD $80. Esta dirección carga el vector de interrupciones de la operación RESET, luego de un reset del procesador va a cargar como primera instrucción la que aquí se encuentre.

Línea 15 comentario

Línea 16 .org FFFE

Aquí le decimos al compilador que a partir de esta dirección ponga las instrucciones siguientes

Línea 17 RESET

Entonces como dato la dirección del label RESET, como escribe LowByte, HighByte por se rel procesador 6502 Little Endian va a ir en la dirección $FFFE $00 y en la $FFFF $80. Esta dirección carga el vector de interrupciones IRQ.

Vamos a compilarlo con vasm.

Y ahora colocamos la eeprom en el programador

Corremos el comando minipro y lo grabamos

Y ahora vamos a estudiar la salida del procesador usando el DUINO PROTOCOL ANALYZER, utilizando el script 001_Arduino_DUINO_PA que ya vimos en el Episodio 3 sobre el DUINO PA y analizaremos el output de nuestro experimento.

Reset seguido de lectura de instrucciones NOP

  1. Conectamos el pin CLK del header CLOCK_555_OUT
  1. Seleccionemos el switch para el reloj paso a paso. 
  1. Presionemos el botón de reset de nuestro CPU y dejemoslo presionado.
  1. Presionar 3 veces el botón de STEP BUTTON de nuestra placa de CLOCK
  1. Soltar el botón de RESET de nuestro CPU
  1. Continuemos presionando el botón de STEP BUTTON de nuestra placa de CLOCK y veremos que cada vez que lo hacemos avanza un ciclo en  nuestro CPU, algunas instrucciones ocupan un ciclo de reloj y otras más
  1. Observamos en detalle los datos que nos da nuestro DUINO PROTOCOL ANALYZER a través del Arduino Mega.

En las primeras tres líneas podemos leer la palabra RESET eso significa que tenemos el botón de reset presionado.

Luego contamos 7 ciclos de reloj que es lo que dura un reset y vemos que en los próximos dos ciclos buscamos el vector de reset en las direcciones FFFC y FFFD.

El procesador va a encender y buscar en las posiciones $FFFC y $FFFD la dirección de la primera instrucción a ejecutar ordenados como Low Byte y High Byte. 

La primera instrucción estará en $8000

En $8000 cargará el opcode A9 de la instrucción LDA de un valor al acumulador

En $8001 tendrá el valor a cargar en el acumulador en este caso $42

En $8002 vamos a tener el valor $4C que es la indicación de hacer una instrucción JMP

En $8003 vamos a tener el valor $00 que es el Low byte de la dirección donde tiene que saltar el JMP

En $8004 vamos a tener el valor $80 que es el High byte de la dirección donde tiene que saltar el JMP con estos dos valores el JMP ya tiene a donde saltar la dirección $8000 y eso es lo que carga en el Program Counter y vuelve a empezar el Loop.

Conclusiones

La Placa RAM_ROM nos permite incluir un chip ROM en nuestro sistema, esto nos da la gran ventaja de poder tener programas en assembler grabados para que nuestra 20c ejecute diferentes tareas. Repasamos cómo funciona el chip ROM, como crear nuestra placa y como hacer un par de experimentos para aprender su funcionamiento.

La placa RAM ROM en funcionamiento

Para ver visualmente cómo conectar la placa RAM_ROM  de la 20c les dejo como siempre un video en detalle de la serie. En este vamos a ver cómo se conectan los pines, y nuestro experimento en forma visual indicando. 

Referencias

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

Arduino IDE

EasyEDA Pro – V2.2.39 

ATMEL AT28C256 datasheet 

74HC00 Datasheet (PDF) – NXP Semiconductors

TL866II USER GUIDE 

David Griffith / minipro · GitLab 

W65C02S 8–bit Microprocessor 

6502 Instruction Set 

vasm portable and retargetable assembler 

WEBSITE

Aquí está el sitio de OsoLabs con todos los videos y artículos.

OsoLabs.tech 

VIDEOS

Aquí el video correspondiente a este capítulo y la lista de todos los videos de esta serie.

https://www.osolabs.tech/the20c

ARTICULOS

Aquí podrán encontrar todos los artículos sobre La 20c:

https://blog.espaciotec.com.ar

https://www.osolabs.tech/the20c

CÓDIGO y DISEÑO DE PLACAS PCB

Todos los ejemplos de código de este artículo los pueden encontrar en:

https://github.com/osolabstech/The20c

OTROS RECURSOS

Como la serie donde vemos todos los juguetes y periféricos de la Commodore que siempre quisimos tener, Los chiches de la Commodore tanto en video como en artículos

https://www.osolabs.tech/los-chiches-de-la-commodore

Los Chiches De La Commodore 

C64 a Fondo – Indice

Y la serie donde comparamos en 6502 contra el 6510 utilizando breadboards.

https://www.osolabs.tech/6502vs6510

6502 vs 6510 estudio detallado y comparación – YouTube 

Y como siempre la serie de Ben Eater del 6502 que es excelente:

Build a 6502 computer | Ben Eater 

Los Chiches de la Commodore 64 – Episodio  3 – El Brazo Robot

En esta ocasión vamos a estudiar uno de los grandes deseos que surgieron en los 80 para usar al máximo la Commodore 64, El Brazo Robot

Vamos a estudiar  que hacía, como funciona por dentro, que circuitos tenía y cómo lograr conectar una Commodore con un circuito casero que vamos a diseñar para que funcione nuestro brazo Robot.

Continuar leyendo “Los Chiches de la Commodore 64 – Episodio  3 – El Brazo Robot”

The 20c – Episodio 1 – CPU 6502

Introducción 

Vamos a empezar esta serie con una descripción del CPU 6502, estudiando para qué sirve cada patita de este chip (que básicamente es el pin out) y cómo funciona su arquitectura interna. Vamos a estudiar que registros tiene y para qué sirven. Luego vamos a seguir contando cómo recrear esta placa PCB desde cero y terminaremos con un experimento que nos permitirá ver nuestra primera instrucción del 6502 ejecutándose.

Continuar leyendo “The 20c – Episodio 1 – CPU 6502”

Los Chiches de la Commodore 64 – Episodio  2- Luces Auto fantástico con un UserPort Breakout Board

En esta ocasión vamos a prender varios leds imitando las luces de KIT el auto fantástico  usando el puerto de usuario de la Commodore 64 para esto vamos a utilizar como Chiche  un User Port Breakout Board.

Estudiaremos que es esta placa, como funciona, cómo conectar un led y escribir un programa en Basic para prenderlo y apagarlo y finalmente cómo conectar una barra de leds y ver las luces estilo auto fantástico.

Continuar leyendo “Los Chiches de la Commodore 64 – Episodio  2- Luces Auto fantástico con un UserPort Breakout Board”

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.

Continuar leyendo “6502 vs 6510 Parte 12 – Como funcionan las interrupciones”

6502 vs 6510 Episodio 11 – El Stack o Pila

Continuamos este estudio comparativo del 6502 vs el 6510 esta vez estudiando que es el Stack o Pila.

Vamos a estudiar en dónde se encuentra, que instrucciones lo usan, qué tamaño tiene y cuál es la mejor forma de crear un programa para poder aprovecharlo,

Qué es el Stack

El stack es una porción de la memoria que se utiliza para el almacenamiento de datos temporales. Tiene la característica de ser de organización LIFO (last in first out) lo que  significa que el último dato que guardamos en el stack es el primero que va a salir de éste. En este ejemplo vemos que aunque el valor A es el primero que entra en el stack, el mismo es el último que sale luego del valor B.

El stack se encuentra ubicado en la página uno de la memoria con lo que sus direcciones comienzan siempre con $01 y tiene 256 bytes de tamaño, en consecuencia sus direcciones de memoria van desde $01FF a $0100.

Para saber en que aparte del stack nos encontramos tenemos una variable llamada Stack Pointer o SP y que siempre  va a indicar la próxima posición vacía del stack. El stack pointer siempre es inicializado en $FF y el $01 de su high byte está harcodeado dentro del 6502 y el 6510.

Algo muy importante a tener en cuenta con el stack es que sólo tenemos 256 posiciones de memoria y que el mismo es circular cuando llenamos la dirección $0100 la próxima es $01FF y si teníamos información útil aquí la misma es sobreescrita perdiéndola, por lo tanto, es muy importante limitar la cantidad de saltos anidados a subrutinas e información que copiamos al stack. A continuación veremos cuantas posiciones de memoria del stack cada instrucción utiliza.

El stack es muy útil en aquellos programas que no trabajan con direcciones de memoria conocidas pero sí con un orden conocido para acceder a la memoria (quien accede primero y quien después). Este tipo de código llamado reentrante se utiliza mucho para poder atender a las interrupciones.

El uso del stack pointer siempre comienza con su inicialización en $01FF.

Registros del Procesador

El Procesador posee algunos registros internos de los cuáles forma parte el stack pointer (recordemos que el stack en sí mismo no es un registro sino un 256 bytes en memoria) y que siempre conviene tenerlos en cuenta.

El acumulador o registro A es un registro de 8 bits que nos va a dejar almacenar valores y realizar operaciones matemáticas en los mismos ya que está conectado tanto a la ALU (unidad aritmético lógica) como a al bus de datos.

Los registros X e Y son registros de 8 bits que nos van a dejar almacenar valores y utilizarlos como índices en distintas operaciones de acceso a memoria.

El Program Counter o PC, es un registro de 16 bits que almacena la dirección de memoria en la cual vamos a ejecutar la próxima instrucción, se divide en sus 8 bits high o PCH y sus 8 bits low o PCL ya que la memoria es de 8 bits en su contenido y para poder “llenarlo”  debemos acceder dos veces a ella.

El stack pointer con 8 bits en su dirección de memoria low y 8 bits grabados como $01 en su parte High el cúal como vimos indica la posición dentro del stack.

El Processor Status Register o P  contiene 8 bits de los cuáles se usan 7

  • C/Carry (Bit 0): Es el noveno bit en las operaciones aritméticas y entre muchos usos refleja el “me llevo uno” de la suma. El valor 1 indica True.
  • Z/Zero (Bit 1): Puesto automáticamente en 1 por el procesador cuando todos los 8 bits de una operación dan como resultado 0 en el registro del acumulador.
  • I/Interrupt Disable (Bit 2): Cuando este bit está en 1 el procesador no acepta interrupciones en su pin IRQ o durante el comando BRK.
  • D/Decimal Mode (Bit 3): Al tener este Flag en 1 el procesador trabaja con aritmética decimal en lugar de aritmética binaria el número 14 en aritmética decimal se representa como 0001 0100 (14) y en aritmética binaria cómo (0000 1110) (0E).
  • B/Break Command(Bit 4): Este bit lo pone en 1 el procesador cuando una interrupción es causada por el comando break, si fuera causada por una interrupción real este bit estaría en 0.
  • Expansion (Bit 5): Reservado para uso en futuras expansiones del procesador. (Nota del Narrador: “y al final nunca se usó”).
  • V/Overflow (Bit 6): Cuando usamos aritmética con signos negativos y positivos sólo tenemos 7 bits disponibles ya que el octavo se utiliza para el signo (0 positivo y 1 negativo). Este bit indica cuando nos “llevamos uno” en una operación aritmética. También se utiliza para reflejar el valor del bit 6 cuando se ejecuta la instrucción bit. Este flag pasa a valor 1 cuando se recibe una transición de High a Low en el pin de SOB (Set Overflow) del procesador.
  • N/Negative Result (Bit7): Se utiliza en aritmética que respeta el signo (cuando usamos números desde el +127 a -128). Este flag siempre va a mostrar el bit 7 del último resultado de todas las operaciones matemáticas que realizamos.

Instrucciones y el Stack

Las instrucciones que usamos en el Stack ocupan utilizan un número fijo de bytes siendo 3 para las interrupciones, 2 bytes para las subrutinas y 1 byte para los datos guardados por el usuario.

El stack comienza en $01FF y se decrementa. Esta nomenclatura que parece rara se creó porque existían implementaciones donde se usaba una sóla página de memoria (el stack era $00FF ubicado en la zero page) entonces los usuarios utilizan espacio para programas y variables comenzando desde la dirección $0000 e incrementando el program counter y el stack decrementa desde $00FF. ¡Con suerte nunca se juntaban en el medio!

Instrucciones que lo usan directamente

Hay 4 instrucciones que utilizamos para enviar y retirar  información al stack. PHA, PLA, PHP, PLP

PHA

El stack sólo puede comunicarse con el Accumulador para recibir datos que nosotros queramos guardar en él, la instrucción para guardar datos por excelencia es Push Accumulator to Stack o PHA. Esta instrucción realiza los siguientes pasos:

  1. Tomar el Opcode de la operación, Incrementar el Program Counter.
  2. Leer el byte de la próxima instrucción y descartarla.
  3. Guardar el byte que se encuentra en el acumulador en la posición de memoria $0100+SP, decrementar SP.

Esto graba en memoria (en la sección del stack) lo que teníamos en el acumulador

PLA

Para poder tomar los datos que teníamos en el stack y volver a traerlos a memoria tenemos la instrucción Pull from Accumulator PLA                

  1. Tomar el Opcode de la operación, Incrementar el Program Counter.
  2. Leer el byte de la próxima instrucción y descartarla.
  3. Incrementar el stack pointer SP.
  4. Guardar el byte que se encuentra la posición de memoria $0100+SP y guardarlo en el Acumulador.  
  5.       

Hay que destacar que el sacar datos del Stack no borra los datos que tenemos en el mismo, sino que sólo desplaza el stack pointer, podríamos si quisiéramos seguir accediendo a estos datos con una instrucción de load desde la dirección $01FF, LDA $01FF.

Processor Status Register

Existe otro byte muy útil de analizar qué es el que mantiene el status de todo el procesador, este byte se denomina Processor Status Register y contiene varios bits útiles como ya hemos visto que representan el estado del procesador.

Si vamos a realizar algunos cambios o realizar operaciones que puedan modificar estos bits y no queremos perder los bits originales tenemos la operación PHP y su contraparte para recuperar los datos PLP.                        

PHP

Para guardar los bits del PSR la instrucción  por excelencia es Push PSR to Stack o PHP. Esta instrucción realiza los siguientes pasos:

  1. Tomar el Opcode de la operación, Incrementar el Program Counter.
  2. Leer el byte de la próxima instrucción y descartarla.
  3. Guardar el byte que se encuentra en el Processor Status Register en la posición de memoria $0100+SP, decrementar SP.

Esto graba en memoria (en la sección del stack) lo que teníamos en el acumulador.

PLP

Para poder tomar los datos que teníamos en el stack y volver a traerlos a memoria tenemos la instrucción Pull from Processor Status Register PLP                

  1. Tomar el Opcode de la operación, Incrementar el Program Counter.
  2. Leer el byte de la próxima instrucción y descartarla.
  3. Incrementar el stack pointer SP.
  4. Leer el byte que se encuentra en la posición de memoria $0100+SP y guardarlo en el Processor Status Register.        

Instrucciones que lo usan indirectamente

Hay 4 instrucciones que usan el stack para almacenar datos sin que nosotros decidamos qué datos son, JSR, RTS y BRK, RTI.

Cada vez que hacemos un salto hacia una subrutina JSR deberemos saber de alguna forma a dónde volver luego de ver el comando de fin de la subrutina RTS. Para saberlo utilizamos el stack donde almacenaremos el byte high y low de la dirección de la próxima instrucción luego del salto. También en forma parecida van a funcionar BRK que indica salto a una interrupción y RTI que es el regreso desde la misma.

JSR

La instrucción JSR o Jump to Subroutine utiliza el stack para guardar información de retorno a la ejecución del programa actual una vez esta subrutina haya finalizada, veamos los pasos que realiza:

  1. Tomar el Opcode de la operación, Incrementar el Program Counter.
  2. Leer el low address byte de la dirección de salto, Incrementar el Program Counter (queda listo para la próxima instrucción).
  3. Operación interna Guardar el Address Low en ADL.
  4. Enviar el byte alto PCH del program counter al Stack en la posición de memoria $0100+SP, decrementar SP, mantener el Address LOW en ADL.
  5. Enviar el byte bajo PCL del último byte de la posición de memoria de la instrucción JSR, generalmente es donde se encontraba el byte High de la dirección de la subrutina, al Stack en la posición de memoria $0100+SP, decrementar SP, mantener el Address LOW en ADL.
  6. Buscar el high byte del address al cual saltar ADH, grabar el stack pointer.
  7. Copiar el byte the low address de la dirección de salto ADL al Program Counter Low (PCL), tomar el ADH y guardarlo en el Program Counter High (PCH).

RTS

La instrucción RTS  nos devuelve desde una subrutina hasta la próxima instrucción a ejecutar después de la llamada a la misma en donde estuvimos ejecutando código. Sus pasos de ejecución son los siguientes:

  • Leer Opcode, incrementar el program counter.
  • Leer el próximo byte y descartarlo, incrementar program counter.
  • Incrementar el stack pointer.
  • Traer byte desde el stack y grabarlo en PCL, incrementar stack pointer.
  • Traer byte desde el stack y grabarlo en PCH.
  • Incrementar program counter.

BRK

El comando BRK simula la ejecución de una subrutina, algo muy importante a tener en cuenta es que él mismo nos va a dar como dirección de regreso Program Counter + 2 con lo que tenemos que tener un cuenta poner un byte que se pueda descartar después del break como ser por ejemplo un NOP, o nuestro programa no va a funcionar ya que volverá a un dirección incorrecta. También podemos utilizar ese byte extra para poder almacenar un código de por qué sucedió el break.

Esta instrucción tarda 7 ciclos antes de comenzar a ejecutar el programa que va a atender la interrupción (handler), y sus pasos de ejecución son los siguientes:

  • Leer Opcode, incrementar rl program counter.
  • Leer el próximo byte y descartarlo, incrementar program counter.
  • Grabar PCH en el stack, setear el flag B, decrementar stack pointer.
  • Grabar PCL en el stack, decrementar stack pointer.
  • Grabar processor status register en el stack, decrementar stack pointer.
  • Leer nuevo PCL desde $FFFE.
  • Leer nuevo PCH desde $FFFF.

RTI

La instrucción RTI nos regresa desde dentro del programa que usamos como handler de la interrupción y reversa alguno de los pasos de la llamada a interrupción original. Estos son sus pasos:

  • Leer Opcode, incrementar el program counter.
  • Leer el próximo byte y descartarlo, incrementar program counter.
  • Incrementar el stack pointer.
  • Traer byte desde el stack y grabarlo en el processor status register, incrementar stack pointer.
  • Traer byte desde el stack y grabarlo en PCL, incrementar stack pointer.
  • Traer byte desde el stack y grabarlo en PCH, incrementar stack pointer.

Un programa que usa el stack

El siguiente programa en código máquina usa el stack a través de 8 instrucciones PHA, PLA, PHP, PLP, JRS, RTS, BRK y RTI. Fue ejecutado en un procesador 6510 original creado en la semana 13 del año 1986.

Si ponemos un analizador de protocolo a ver los resultados de estos comandos podemos observar los siguientes resultados, el formato de los mismos sera:

10    0                  Descartar no lo vamos a utilizar en el análisis

1000000000000000     Address Bus en Binario

10100010                  Data Bus en Binario

8000                          Address Bus en Hexadecimal

r                          Operación de Lectura “r” o escritura “W”

a2                          Data Bus en Hexadecimal

ldx                          Si es una instrucción, el correspondiente mnemónico de assembler si no es un dato o dirección de memoria y se lee literal.

Inicializar el Stack pointer en $01FF

 

Lo primero que pasa con este programa es que comenzamos en la dirección 8000, cargamos ff en el registro X y lo transmitimos al Stack, la última operación que aparece como a9 es un ciclo que gasta el procesador en guardar el valor FF en el registros interno del Stack Pointer.

PHA y PLA

En este segmento del programa vemos cómo almacenar dos valores desde el acumulador al stack y como traerlos.

Cargamos el valor 64 al acumulador.

10    0 1000000000000011    10101001 8003     r a9 lda

10    0 1000000000000100    01100100 8004     r 64

Lo transferimos al stack y el próximo ciclo de  instrucción 8006 se pierde ya que es utilizado internamente

10    0 1000000000000101    01001000 8005     r 48 pha

10    0 1000000000000110    10101001 8006     r a9 lda

Escribimos el valor 64 en la dirección 01ff del stack en memoria, el stack pointer queda en 01fe

10    0 0000000111111111    01100100 01ff     W 64

Cargamos el valor 99 al acumulador.

10    0 1000000000000110    10101001 8006     r a9 lda

10    0 1000000000000111    10011001 8007     r 99

Lo transferimos al stack y el próximo ciclo de  instrucción 8009 se pierde ya que es utilizado internamente

10    0 1000000000001000    01001000 8008     r 48 pha

10    0 1000000000001001    01101000 8009     r 68 pla

Escribimos el valor 99 en la dirección 01fe del stack en memoria, el stack pointer queda en 01fd

10    0 0000000111111110    10011001 01fe     W 99

Pedimos transferir un valor desde el stack (el 99) y la próxima instrucción 800a se descarta

10    0 1000000000001001    01101000 8009     r 68 pla

10    0 1000000000001010    01101000 800a     r 68 pla

Se escribe la posición actual del stack pointer 01fd en el address bus y se descarta el dato y se incrementa el stack pointer.

10    0 0000000111111101    01110000 01fd     r 70

Se lee la posición actual del stack pointer 01fe y se guarda el valor en el acumulador

10    0 0000000111111110    10011001 01fe     r 99

Pedimos transferir un valor desde el stack (el 64) y la próxima instrucción 800b se descarta

10    0 1000000000001010    01101000 800a     r 68 pla

10    0 1000000000001011    10101001 800b     r a9 lda

Se lee de la posición actual del stack pointer 01fe y se descarta y se incrementa el stack pointer.

10    0 0000000111111110    10011001 01fe     r 99

Se lee la posición actual del stack pointer 01ff y se guarda el valor en el acumulador

10    0 0000000111111111    01100100 01ff     r 64

PHP y PLP

En este segmento del programa vemos cómo almacenar dos veces el Processor Status Register, la segunda vez lo cambiamos para que el flag de testeo Zero esté en 1 asignando previamente al acumulador un valor de 0.  Para terminar el programa traemos desde el stack esos valores.

Cargamos el valor 64 al acumulador.

10    0 1000000000001011    10101001 800b     r a9

10    0 1000000000001100    01100100 800c     r 64

Pedimos transferir  al stack el Processor Status Register y el próximo ciclo de  instrucción 800e se pierde ya que es utilizado internamente

10    0 1000000000001101    00001000 800d     r 08 php

10    0 1000000000001110    10101001 800e     r a9

Escribimos el valor 3d en la dirección 01ff del stack en memoria, el stack pointer queda en 01fe

Este valor representa el número binario 0011 1101 usando los flags del procesador es

N=0,V=0,E=1,B=1,D=1,I=1,Z=0,C=1 Podemos ver que el flag Z está en cero ya que el resultado de la última operación es 64 distinto de cero.

10    0 0000000111111111    00111101 01ff     W 3d

Cargamos el valor 0 al acumulador.

10    0 1000000000001110    10101001 800e     r a9

10    0 1000000000001111    00000000 800f     r 00

Pedimos transferir  al stack el Processor Status Register y el próximo ciclo de  instrucción 8011 se pierde ya que es utilizado internamente.

10    0 1000000000010000    00001000 8010     r 08 php

10    0 1000000000010001    00101000 8011     r 28

Escribimos el valor 3f en la dirección 01fe del stack en memoria, el stack pointer queda en 01fd

Este valor representa el número binario 0011 1111 usando los flags del procesador es

N=0,V=0,E=1,B=1,D=1,I=1,Z=1,C=1 Podemos ver que el flag Z está en uno ya que el resultado de la última operación es cero.

10    0 0000000111111110    00111111 01fe     W 3f

Pedimos transferir un byte desde el stack (el 3f) al Processor Status register y la próxima instrucción 8012 se descarta.

10    0 1000000000010001    00101000 8011     r 28 php

10    0 1000000000010010    00101000 8012     r 28

Se lee de la posición actual del stack pointer 01fd y se descarta y se incrementa el stack pointer.

10    0 0000000111111101    01110000 01fd     r 70

Se lee la posición actual del stack pointer 01fe y se guarda el valor en el Processor Status register

10    0 0000000111111110    00111111 01fe     r 3f

Pedimos transferir un byte desde el stack (el 3f) al Processor Status Register y la próxima instrucción 8013 se descarta.

10    0 1000000000010010    00101000 8012     r 28 php

10    0 1000000000010011    00100000 8013     r 20

Se lee de la posición actual del stack pointer 01fe y se descarta y se incrementa el stack pointer.

10    0 0000000111111110    00111111 01fe     r 3f

Se lee la posición actual del stack pointer 01ff y se guarda el valor en el Processor Status Register

10    0 0000000111111111    00111101 01ff     r 3d

JSR y RTS

En este segmento del programa vemos cómo saltar hacia una subrutina., cómo se almacena la dirección de retorno y que pasos se dan en el programa para volver al mismo momento desde donde partió el llamado de la subrutina.

Comienza nuestro programa en la dirección $8013 con un JSR para saltar a la subrutina, pero en lugar de tener a continuación una dirección de memoria tenemos el byte 1c, este es el low address en el que comienza nuestra subrutina con un lda #$44 como podemos ver al hacer un dump en binario de nuestro código, seguido de este tenemos el byte 80, lo que apunta nuestra subrutina la dirección $801C ya que siempre las direcciones de memoria de JSR son absolutas.

Se lee el byte de la dirección del futuro Low address $1C, esto va a pasar a PCL en algunos ciclos más.

10    0 1000000000010011    00100000 8013     r 20 jsr

10    0 1000000000010100    00011100 8014     r 1c

Se lee de la posición actual del stack pointer 01ff  y se descarta.

10    0 0000000111111111    00111101 01ff     r 3d

Se escribe PCH, el High Byte del program counter en la posición actual del stack pointer 01ff  y se decrementa el stack pointer que queda en 01fe.

10    0 0000000111111111    10000000 01ff     W 80

Se escribe PCL, el Low Byte del program counter en la posición actual del stack pointer 01fe  y se decrementa el stack pointer que queda en 01fd.

10    0 0000000111111110    00010101 01fe     W 15

Se lee la instrucción en la posición de memoria  del high byte de la subrutina y se copia al PCH para poder hacer el jump a 801C una vez actualizado el program counter.

10    0 1000000000010101    10000000 8015     r 80

Comienza la ejecución de nuestra subrutina cargando el valor 44 en el acumuladore

10    0 1000000000011100    10101001 801c     r a9 lda

10    0 1000000000011101    01000100 801d     r 44

Se carga la instrucción sta para grabar el acumulador en la posición de memoria 2003

10    0 1000000000011110    10001101 801e     r 8d sta

10    0 1000000000011111    00000011 801f     r 03

10    0 1000000000100000    00100000 8020     r 20

Se escribe la posición de memoria 2003 

10    0 0010000000000011    01000100 2003     W 44

Se pide volver de la subrutina con la instrucción rts, se lee la próxima instrucción en la posición 8022 y se descarta.

10    0 1000000000100001    01100000 8021     r 60 rts

10    0 1000000000100010    10101001 8022     r a9

Se lee de la posición actual del stack pointer 01fd y se descarta y se incrementa el stack pointer.

10    0 0000000111111101    01110000 01fd     r 70

Se lee de la posición actual del stack pointer 01fe el low byte del PCL a y se incrementa el stack pointer.

10    0 0000000111111110    00010101 01fe     r 15

Se lee de la posición actual del stack pointer 01ff el high byte del PCH a y se incrementa el stack pointer.

10    0 0000000111111111    10000000 01ff     r 80

El Program Counter queda en 8015 y se busca la próxima instrucción en esa dirección

10    0 1000000000010101    10000000 8015     r 80 brk

BRK y RTI

En este segmento del programa vemos cómo se ejecuta una interrupción brevemente, hablaremos más de interrupciones como suceden y para que se usan en un próximo capítulo.

  

Este programa causa una interrupción por software llamada break la cuál hace que el procesador vaya a las direcciones $FFFE y $FFFF para buscar los bytes que indican que programa comenzar a ejecutar, este en nuestro caso es el que comienza en la etiqueta irq:

El break nos va a dar como dirección de regreso Program Counter + 2 con lo que tenemos que tener un cuenta poner un byte que se pueda descartar después del break como ser por ejemplo un NOP o nuestro programa no va a funcionar ya que volverá a un dirección incorrecta. También podemos utilizar ese byte extra para poder almacenar un código de por qué sucedió el break.

Esta instrucción tarda 7 ciclos antes de comenzar a ejecutar el programa que va a atender la interrupción (handler). A continuación nuestro programa corregido (notar el NOP) y veremos todos nuestros address familiares corridos un byte.

Nuestro segmento de programa entonces comienza en $8018 con el comando BRK cuyo código es $00, lee a continuación el próximo byte y lo descar

10    0 1000000000011000    00000000 8018     r 00 brk  

10    0 1000000000011001    11101010 8019     r ea NOP

Escribe en el stack en las posiciones 01ff a 01fe la posición a la cual regresar $801a, nótese que si no tuviéramos la instrucción NOP regresaremos un byte antes a nuestro programa y este NO FUNCIONARIA.

10    0 0000000111111111    10000000 01ff     W 80

10    0 0000000111111110    00011010 01fe     W 1a

Escribe en el stack el Processor Status Register

10    0 0000000111111101    00110110 01fd     W 36

Lee el vector fffe y ffff (low y high byte) para saber dónde está el handler o programa que manejará la interrupción, en este caso en la posición 8023.

10    0 1111111111111110    00100011 fffe     r 23

10    0 1111111111111111    10000000 ffff     r 80

Con el program counter en 8023 correspondiente a la etiqueta irq: carga la primera instrucción para guardar el valor 55 en el acumulador.

10    0 1000000000100011    10101001 8023     r a9 lda

10    0 1000000000100100    01010101 8024     r 55

Lee una instrucción para guardar el valor del acumulador en la posición de memoria 2005

10    0 1000000000100101    10001101 8025     r 8d sta

10    0 1000000000100110    00000101 8026     r 05

10    0 1000000000100111    00100000 8027     r 20

Escribe en la posición de memoria 2005 el valor 55

10    0 0010000000000101    01010101 2005     W 55

Lee la instrucción rti para salir del handler de interrupciones.

10    0 1000000000101000    01000000 8028     r 40 rti

Ciclo Interno, Lee el próximo byte y lo descarta.

10    0 1000000000101001    00000000 8029     r 00

Se lee de la posición actual del stack pointer 01fc y se descarta y se incrementa el stack pointer.

10    0 0000000111111100    00101101 01fc     r 2d

Se lee de la posición actual del stack pointer 01fd y se copia la información al Processor Status Register y se incrementa el stack pointer.

10    0 0000000111111101    00110110 01fd     r 36

Se lee la posición actual del stack pointer 01fe y se copia la información al PCL y se incrementa el stack pointer.

10    0 0000000111111110    00011010 01fe     r 1a

Se lee la posición actual del stack pointer 01fe y se copia la información al PCH y se incrementa el stack pointer.

10    0 0000000111111111    10000000 01ff     r 80

Se resumen la ejecución del programa en un loop infinito que salta a la posición de memoria 8019 en forma recurrente,

10    0 1000000000011010    01001100 801a     r 4c jmp

10    0 1000000000011011    00011010 801b     r 1a

10    0 1000000000011100    10000000 801c     r 80

10    0 1000000000011010    01001100 801a     r 4c jmp

10    0 1000000000011011    00011010 801b     r 1a

10    0 1000000000011100    10000000 801c     r 80

10    0 1000000000011010    01001100 801a     r 4c jmp

10    0 1000000000011011    00011010 801b     r 1a

10    0 1000000000011100    10000000 801c     r 80

Cómo se incrementa el Stack dentro del procesador 6510

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.

Primero saber que para decrementar en realidad se realiza la suma al complemento por lo que si queremos restarle uno a FD es lo mismo que sumarle FF

FD -1 = FC

FD + FF = FC

Veamos algunas de las señales internas utilizadas.

  1. En el bus interno de Address Low ADL El Stack Pointer le carga su valor de $FD en nuestro ejemplo. mediante la señal S/ADL.
  2. Este valor $FD es cargado al registro B de la ALU mediante la señal ADL/ADD
  3. Se genera el valor $FF y se carga en el bus SB,
  4. Este valor $FF se carga del bus SB al bus registro A de la ALU mediante la señal SB/ADD.
  5. Finalmente con la señal SUMS se realiza la suma de ambos valores.
  6. El resultado de la alu queda en el bus SB mediante la señal ADD/SB(0-6) y ADD/SB(7) que cargan los primeros 7 bits y el último en el bus SB respectivamente.
  7. El stack pointer es cargado mediante la señal SB/S con lo que es actualizado con el valor decrementado
  8. Luego la señal S/ADL cargar el stack pointer en el registro Low interno y la señal ADL/ABL carga finalmente los 8 bits inferiores del address bus con el valor del Stack Pointer.

Más abajo que esto no podemos llegar adentro de un 6502 (Más profundo y llego a China)., para más información busquen el Diagrama de Hanson del interior del 6502 y el proyecto visual6502 ambos sitios incluidos en las referencias de este artículo.

Cómo funciona en el Commodore 64

El stack es utilizado en la Commodore 64 para todas estas instrucciones que vimos originalmente con exactamente las mismas funciones y parámetros. Todo el código que vimos en este artículo fue ejecutado en un MOS 6510 de la semana 13 de 1986.

El stack también comienza en la dirección $01FF y el valor del stack pointer es inicializado en la rutina de inicialización de la commodore:

.,FCE2 A2 FF    LDX #$FF        START  LDX #$FF

.,FCE4 78       SEI                    SEI

.,FCE5 9A       TXS                    TXS

.,FCE6 D8       CLD                    CLD

.,FCE7 20 02 FD JSR $FD02              JSR A0INT       ;TEST FOR $A0 ROM IN

.,FCEA D0 03    BNE $FCEF              BNE START1

.,FCEC 6C 00 80 JMP ($8000)            JMP ($8000)     ; GO INIT AS $A000 ROM WANTS

.,FCEF 8E 16 D0 STX $D016       START1 STX VICREG+22   ;SET UP REFRESH (.X=<5)

.,FCF2 20 A3 FD JSR $FDA3              JSR IOINIT      ;GO INITILIZE I/O DEVICES

.,FCF5 20 50 FD JSR $FD50              JSR RAMTAS      ;GO RAM TEST AND SET

.,FCF8 20 15 FD JSR $FD15              JSR RESTOR      ;GO SET UP OS VECTORS

                                ;

.,FCFB 20 5B FF JSR $FF5B              JSR CINT        ;GO INITILIZE SCREEN

.,FCFE 58       CLI                    CLI             ;INTERRUPTS OKAY NOW

.,FCFF 6C 00 A0 JMP ($A000)            JMP ($A000)     ;GO TO BASIC SYSTEM

Esta rutina es llamada cada vez que se ejecuta un reset a través del vector del mismo nombre que está en las direcciones $FFFC, $FFFD

.:FFFA 43 FE                    .WOR   NMI             ;PROGRAM DEFINEABLE

.:FFFC E2 FC                    .WOR   START           ;INITIALIZATION CODE

.:FFFE 48 FF                    .WOR   PULS            ;INTERRUPT HANDLER

Estudio visual

Para poder estudiar visualmente cómo funciona el stack les dejo esta video que complementa al artículo.

El Stack – 6502 vs 6510 Episodio 11

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 

VIDEOS

Video de la serie 6502 vs 6510 Episodio 11 – El Stack

El Stack – 6502 vs 6510 Episodio 11

Aquí tiene acceso a toda la serie:

6502 vs 6510 estudio detallado y comparación 

PAPERS

MCS6500 Microcomputer Family Programming Manual

MOS 6500 Hardware Manual  

W65C02S 8–bit Microprocessor 

6510 MICROPROCESSOR WITH I/O 

6502 Instruction Set 

Load and Run from 6502 ASM (1/2) | C64 OS 

Mapping The Commodore 64

C64 Kernal Disassemble 

Visual 6502 in JavaScript

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/carlinhocr/6502_vs_6510

C64 a Fondo – Indice

A continuación les dejo los links a los artículos anteriores de la serie

6502 vs 6510 Episodio 10 – Conectando una Memoria Static RAM

Continuamos este estudio comparativo del 6502 vs el 6510 esta vez conectando una memoria Static RAM del modelo 62256 que posee 32kb.

Vamos a estudiar cómo conectar esta memoria tanto a un 6510 con CIA 6526  como a un 6502 con VIA 6522, incluiremos todas las rutinas de assembler para poder probar la memoria, sus conexiones físicas y  timing de la memoria con el procesador, punto central de su funcionamiento.

La Memoria HM62256B

Esta memoria es una static ram, esto implica que los datos que esta posee no necesitan ser refrescados cada cierta cantidad de ciclos de reloj sino que los mismos se conservan mientras esta no pierda electricidad.

Su denominación de 256 refiere a sus 256Kbits disponibles, estos están organizados en 32768 entradas de 8bits cada una lo que conocemos normalmente como 32Kbytes.

PinOut

Este chip viene en formato DIP (Dual Inline Pins) de 28 pines y es muy parecido en su layout a la eeprom AT28C256 que vimos en un artículo anterior.

A14 – A0: Estos pines nos permiten seleccionar qué registro de ocho bits queremos acceder dentro de nuestra memoria, al ser 15 pines podemos direccionar 2ˆ15 = 32768 registros de 8 bits. Estos pines se conectan al bus de direccionamiento.

I/O 0 a I/O 7: Los pines de I/O es donde vamos a ver el contenido de cada registro previamente seleccionado para leer la memoria, o donde vamos a enviar los datos que tenemos para escribir la memoria. Estos pines se conectan al bus de datos.

VCC: En este pin es donde el chip espera una alimentación de +5Volts

GND: Este es el pin de referencia a tierra del chip

/WE: El pin de write enable al recibir una señal de low o 0 Volts permite grabar en los registros de la memoria. Como la estamos utilizando como una ROM conectamos este pin directamente a +5 Volts para que sea de sólo lectura. La barra / significa que este pin es active low con lo cual espera 0 Volts para activarse

/OE: El pin de output enable conecta o desconecta los pines de I/O del bus de datos. Si el pin está en +5 Volts la memoria se desconecta del bus de datos poniendo sus pines de datos en un estado de alta impedancia. La barra / significa que este pin es active low con lo cual espera 0 Volts para activarse

/CE: El pin de chip enable conecta o desconecta los pines del chip para una lectura o escritura trabajando en conjunto con /OE y /WE. Es active low con lo cual espera 0 Volts para activarse

Timing para una lectura

Cuando un procesador y una memoria necesitan comunicarse ya sea para lectura o escritura hay dos tiempos generales que tienen que ser compatibles: el tiempo en que la memoria responde y el tiempo que el procesador puede esperar. Para poder leer o escribir tenemos que realizar una combinación de 3 pines /WE /OE /CE. En el caso de una lectura WE debe estar en High y OE y CE en low, para una escritura los 3 pines deben estar en LOW.

Timing de la Memoria en una lectura

Para poder hacer una lectura de la memoria, primero el procesador debe poner en el address line o bus de direccionamiento la dirección  donde está el dato que quiere leer, esta dirección consiste en los unos y ceros o los highs y lows de los pines A15 a A0.

La memoria no tiene inmediatamente disponibles los datos elegidos sino que tarda en buscar el dato y en poner el mismo en el bus de datos con sus 8 bits representados por los pines D7 a D0 y tarda en que estos estén estables, que sean válidos y  que reflejen el valor interno en la memoria por lo que el procesador tiene que esperar un tiempo hasta que estos datos sean válidos y recién ahí leerlos, en el caso del datasheet del ejemplo 70ns como mínimo debe ser el tiempo de espera del procesador.

Hagamos el análisis paso por paso utilizando el siguiente diagrama.

Primero el procesador tiene que colocar los 16 bits del address line en forma correcta pero estos puede que no sean seteados al mismo tiempo o en algún orden específicos con lo que el bit 1 puede setearse luego el 15, luego el 12, etc. Tenemos que esperar hasta el punto donde comienza TRC o Read Cycle Time que es el momento donde el address bus tiene los 16 bits en forma correcta.

El procesador deberá esperar un tiempo tAA o Address Access Time, que es el tiempo para que los datos en el bus de datos sean válidos y con valores correctos. Cuando el tiempo tAA termina recién ahí los datos o bits o highs y lows que están en el bus son válidos y representan la dirección deseada.

Este tiempo  posee dos subcomponentes tACS o chip Select to access time que es el tiempo que tarda el chip en activarse cuando recibe una señal low en el pin de Chip Select y también tOE o Output Enable to Output Valid, qué es el tiempo que tarda en activarse los pines de output luego de recibir un low en el pin de Output enable y que estos pines reflejen el valor correcto del contenido de la memoria en el bus de datos..

¿Cómo sabemos cuánto puede tardar como máximo el chip en darnos datos válidos una vez que tenemos un address válido en el bus de direccionamiento? Con una tablita de tiempos de acceso disponible en el datasheet de cada chip.

Si vemos para el chip de la familia HB62256B si termina el mismo en -7 el tiempo máximo de address access time o tAA es de 70 nanosegundos, podemos deducir que es tiempo máximo de tACS el chip select to access time o lo que tarda en activarse el chip ya que este timer tarda 70 ns y el de tOE de Output enable to output valid solo tarda 40ns como máximo.

tAA = 70 ns

tACS = 70ns

tOE = 40ns

Otro tiempo importante que vamos a utilizar en el futuro es el tOH o el Output hold time from address change, este tiempo es cuánto los datos vamos a mantenerse como válidos desde que cambió el address en el bus de direccionamiento, este es de 5ns

tOH = 5ns

Ahora cómo sabemos si el procesador que utilizamos puede esperar 70 nanosegundos? Estudiando el diagrama de timing del mismo,

Timing del Procesador 6502 en una lectura

El siguiente es el diagrama de tiempos del procesador 6502 hecho por Western Design Center. El problema con este diagrama es que mezcla los tiempos de escritura y de lecturas al mismo gráfico por lo que construí un diagrama simplificado para poder entenderlos mejor.

Por otro lado la velocidad de cada uno de estos intervalos va a depender de a que voltaje nosotros manejemos el cpu, como estamos usando +5 Volts esa es la columna que utilizaremos. Estos voltajes nos van a dar un máximo de 14Mhz para correr nuestro CPU pero lo vamos a estar corriendo a 1Mhz.

El ciclo del Reloj

El primer tiempo que nos interesa saber es el de un ciclo completo de reloj, este está representado en el diagrama como PHI2 y se divide en tPWL y tPWH (Clock Pulse Width Low y High respectivamente). Al usar un reloj de 1Mhz vamos a tener disponibles 1000 nanosegundos para todo el ciclo completo de reloj.

(1)

tPWL va de 0ns a 500ns

tPWH va de 500ns a 1000ns

El mínimo de tiempos de estos intervalos podría ser de 35ns cada uno o sea 70ns de ciclo de reloj si lo corriéramos a 14Mhz pero sabemos que por lo menos necesitamos 70ns para que nuestra memoria nos de los datos con lo cual esta velocidad no es adecuada.

Las preguntas que debemos responder primero para ver si podemos esperar esos 70ns que tarda en acceder a los datos la memoria que estamos utilizando es cuando el procesador configura el address en sus pines y cuando r ealiza la lectura.

Estableciendo el Address en el Bus de Direccionamiento

El segundo tiempo que vamos a tener que estudiar es el tADS o Address Setup Time , es el tiempo que le toma al cpu estabilizar los highs y lows en los pines del bus de direcciones. Y el tercer tiempo es el tAH o Address Hold Time, por cuánto tiempo esos highs y lows son válidos en el bus de direcciones.

(2) tADS = 30ns

(3) tAHT = 10ns

El tADS comienza en el falling edge del comienzo del ciclo del reloj.

El tAHT se mantiene desde el falling edge (transición de High a Low) del final ciclo del reloj.

Leyendo los datos

El cuarto tiempo a estudiar es el tDSR o Data Setup Time, que es cuánto tiempo tardan en estabilizarse los highs y lows en el bus de datos y el quinto tiempo es el tDHR o data Hold read time o cuánto tiempo esos datos són válidos.

(4) tDSR = 10ns

(5) tDHR = 10ns

El tDSR termina desde el falling edge (transición de High a Low) del ciclo del reloj en ese falling edge es cuando ocurre la lectura.

El tDHR son por lo menos 10ns desde el momento de la lectura.

Tenemos que asegurarnos que la RAM esté dando datos válidos durante tDSR + tDHR. Tenemos que asegurarnos que la RAM esté dando datos válidos durante tDSR + tDHR.

Si hacemos un esquema podemos ver:

0ns    a   30ns necesitamos que el address sea estabilizado tADS

30ns  a   1010 ns el address el válido tAHT

990ns a 1010ns los datos tiene que ser válidos tDSR + tDHR

Con lo cual la ram tiene 990 – 30 = 960ns para poder dar los datos en el bus, pero como vimos la RAM sólo tarda 70ns como máximo para darnos los datos por lo que tenemos mucho tiempo disponible.

Pero por cuánto tiempo la RAM mantiene los datos válidos en el bus? Para esto está el timer tOH de la RAM que es de 5ns a partir de que cambia la dirección de la ram, pero la dirección cambia recién en el ns 1010 que es cuando expira el timer de address hold time del procesador lo que nos da unos 5ns extras para la lectura.

990 ns a 1015ns la RAM da valores de highs y lows válidos en el bus de datos

Por esto podemos hacer la lectura por que el procesador requiere de 10ns después del momento de la lectura y la RAM mantiene los datos válidos por 15ns

De forma similar a como hicimos este análisis podemos realizar lo mismo para la escritura de la memoria variando solo algunos valores de los parámetros.

Un gran lugar para poder ver alternativamente como funcionan los diagramas de tiempo del 6502 es este sitio donde se ve muy bien visualmente Visual Guide to 65xx CPU Timing 

Timing para una escritura

Con la información que ya tenemos de interpretar cómo se hace una lectura encaremos la escritura de datos en la memoria.

Timing de la Memoria en una escritura

Veamos este nuevo diagrama de tiempos para la escritura.

Y también los nuevos valores mínimos y máximos para estos parámetros

Este diagrama asume que el pin de OE output enable está fijo en Low y según la nota 4 la escritura se va a realizar cuando CS y WE ambos estén el Low.

Comienza el tiempo tWP o Write Pulse Width cuando el último pin entre WE y CS entren en low y dura hasta que el primero de ellos pase a High.

tWP = minimo de 50ns

Los datos deben ser válidos en el bus por lo menos desde tDW o Data Write time overlap y mantenerse válidos por un período tDH o Data Hold form write time

tDW = 30ns

tDh = 0ns

Con lo cual por lo menos 30ns antes de que WE o CS pasen a ser High los datos deben mantenerse como válidos.

Si analizamos lo que puede pasar, el bus de datos puede tener cualquier información errónea sin problemas y esos datos se escriben en la ram, pero por lo menos 30ns antes de que se termine la escritura los datos deben ser válidos ya que estos quedarán en la RAM, estos datos válidos pueden ser mantenidos por 0ns enel bus si queremos ya que ya han sido escritos ya que el mínimo de tDH es cero.

Timing del Procesador 6502 en una escritura

Veamos este nuevo diagrama de tiempos para la escritura.

Sabemos que los siguiente tiempos se cumplen debido a nuestro análisis anterior:

0ns    a   30ns necesitamos que el address sea estabilizado tADS

30ns  a   1010ns el address el válido tAH

1000 ns a 1010ns tDHR

Los datos se vuelven inválidos al final de tDHR que coincide con tAH y con un nuevo timer tDHW o Write Data Hold Time

tDHW = 10ns desde el falling edge fin del clock cycle

1000 ns a 1010ns tDHW

El write ocurre en el falling edge del final de clock cycle. Pero para que la escritura sea correcta los pines de CS o WE tienen que ser high antes de que el address, los datos o el write hold time sean inválidos.

Si conectamos el pin de chip select CS a cualquier de las address lines estaríamos en problemas ya que debemos asegurar que el pin CS sea high mientras todavía todos los pines de address son válidos y todos los pines de datos son válidos, pero no tenemos forma de poder apagar un pin antes que los otros asegurándonos que sea siempre así.

Lo mismo nos sucede con el pin de R/W del procesador no hay forma de garantizar que vaya a cambiar antes que los pines de address.

Con lo que nada nos asegura que CS o WR pasen a ser High (terminando la escritura) antes de que el address y los datos seán inválidos.

Para solucionar esto podemos hacer que el CS chip select pin sólo sea LOW durante el ciclo de pulso alto del reloj o tPWH de 500 a 1000 ns, de esta forma nos aseguraremos que el address bus tenga direcciones válidas y que unos nanosegundos tDW antes de apagarse el CS tenemos todavía datos válidos, ya que los hold timers de tAH y tDW nos mantendrían valores válidos en address y data bus respectivamente aún después de poner el High el pin de CS. En nuestro ejemplo serían 10ns extras.

Para lograr esto podemos conectar el pin a15 si lo usáramos para seleccionar nuestro pin de chip select conectado a través de dos compuertas nand de forma tal que solo en el pulso high del reloj y cuando el pin a15 sea cero el pin de la memoria de chip select reciba un cero o low.

Al agregar dos compuertas nand debemos sumar un tiempo más que el que tarda la compuerta en evaluar sus inputs y darnos un output, este tiempo se llama maximum propagation delay (tPHL).

En el caso de las compuertas que utilizamos este es de 25ns al usar 2 vamos a tener como máximo 50ns de delay, lo que implica que la señal de chip select va a ir a low 50 ns después de que si no usáramos las compuertas, en este setup ese tiempo no influye ya que tenemos 960ns disponibles con el address valid antes de que los datos estén válidos (llendo de los 30ns a los 990ns) esto nos llevaría sólo al intervalo 80ns a 990ns no implica problema alguno.

Timing en el procesador 6510

El timing en el 6510 es bastante más lento que en el 6502 y eso debemos tomarlo en cuenta, observemos estos valores y diagramas del datasheet original, el primer diagrama es de lectura y el segundo tiempos de escritura.

También tenemos algunos tiempos diferentes que los del 6502 más moderno

Así por ejemplo podemos observar el timer tADS o Address Setup Time el cual tarda 300ns en lugar de los 30ns del 6502 moderno. Si tuviéramos que hacer la cuenta con los tiempos para nuestra memoria ahora deberíamos comenzar así:

tADS = 300ms

tAH = 10ms

tDHR=tHR=10

0ns    a   300ns necesitamos que el address sea estabilizado tADS

300ns  a   1010ns el address el válido tAH

1000 ns a 1010ns tDHR

Y nuestra RAM muestra datos válidos desde

(tADS+tAA) a (tAH+tOE) = 300ns+70ns a 1010ns + 5ns

370ns a 1015ns la RAM da valores de highs y lows válidos en el bus de datos

Si nos referimos al manual de Hardware de la línea de procesadores 6500 él mismo nos dice que a 1Mhz el address está estable. sí o sí,  300 nano segundos después de que comienza la fase uno y los datos deben estar estables al menos 100 nanosegundos antes de que termine la fase dos de nuestro ciclo de reloj. Esto nos da 575 ns  para poner los datos en el bus de datos.

Y podemos observar los diagramas para timings de Read y Write respectivamente.

Read Timing Diagram

Write Timing Diagram

Cómo funciona en el Commodore 64

En nuestra querida Commodore 64 no estamos usando una memoria estática como la de este empleo si no que es dynamic ram, esta misma debe ser refrescada constantemente de lo que se encarga el chip de video VIC2. Esta memoria se selecciona por filas y columnas utilizando las señales de RAS y CAS.

Tampoco tenemos un sólo chip sino 8 chips cada uno de 64536 entradas y 1 bit de datos en cada entrada, con lo que si queremos representar un byte necesitamos los 8 chips y 1 bit de cada uno de ello, de ahí que cuando se rompe un chip de ram nada funciona ya que afecta al contenido de un bit en cada posición de la RAM.

Estudio visual

Para poder estudiar visualmente cómo conectar una ram estática y programarla  les dejo esta video que complementa al artículo.

RAM con 6510/CIA y 6502/VIA – 6502 vs 6510 Parte 10

Referencias

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

WebSite OsoLabs 

VIDEOS

Video de la serie 6502 vs 6510 Parte 10 – RAM

RAM con 6510/CIA y 6502/VIA – 6502 vs 6510 Parte 10

Aquí tiene acceso a toda la serie:

6502 vs 6510 estudio detallado y comparación 

PAPERS

W65C02S 8–bit Microprocessor 

6510 MICROPROCESSOR WITH I/O 

6502 Instruction Set 

Visual Guide to 65xx CPU Timing 

HM62256B Series 

MOS 6500 Family Hardware Manual

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

C64 a Fondo – 6502 vs 6510 Episodio 9 – 6502 con Display LCD

Continuamos este estudio comparativo del 6502 vs el 6510 conectando un LCD de 16 caracteres x 2 líneas y programándolo con un mensaje. Vamos a estudiar cómo conectar un VIA 6522 y un CIA 6526 al lcd HD4470U de Hitachi y utilizando nuestro 6502 y nuestro 6510 mostrar el texto “OsoLabs” en el display en dos maquetas breadboard simultáneas.

El LCD Hitachi HD44780U

Nuestro principal display es el LCD de Hitachi el mismo posee el siguiente diagrama esquemático.

Este LCD tiene 16 caracteres que se pueden mostrar en dos líneas y cada carácter puede ser de 5×8 o 5×10 puntos. Posee internamente el dibujo o font de varios tipos de caracteres que se eligen con valores correspondientes mayormente al ASCII. Estos caracteres incluyen el alfabeto tradicional, caracteres japoneses y símbolos. El LCD puede ser controlado por un procesador de 4 bits o uno de 8 bits, nosotros lo utilizaremos en modo 8 bits por estar usando la línea 6502/6510.

En cuanto a velocidad es compatible con un bus de datos de hasta 2Mhz, nosotros vamos a estar utilizando hasta 1Mhz el máximo de velocidad de nuestro 6510.

PinOut del LCD

El siguiente esquema muestra y explica el pin-out

VSS (Ground): conexión a tierra de nuestro LCD

VDD: conexión a 5 Volts +

VE: Pin de contraste, se conecta normalmente a un potenciómetro en su pin de polo y el otro pin va directo a tierra.

Register Select RS: Nos permite elegir si escribir al Instruction Register usando el valor 0 o al Data Register usando el valor 1

Read/Write : Permite escribir si el valor es cero y leer si el valor es 1

Enable: Comienza la lectura o escritura sacando los pines DB7 a DB0 de su modo de triestado.

DB7 a DB0: Bus datos para escribir o leer. En el modo de operación de 4 bits sólo se usando los pines DB7 a DB4

A : Ánodo (pin positivo +5 Volts) para encender la luz de backlight

K: Cátodo (pin negativo 0 Volts) para encender la luz de backlight

Cómo leer y cómo escribir nuestro LCD

Hay tres pines que funcionan para poder leer o escribir los registros internos y la pantalla de nuestro LCD, estos son RS, RW y E. En la siguiente Tabla especificamos cómo realizar cada operación.

RS = 0 /R/W = 0 E=1 permiten enviar una instrucción al LCD, escribiendo el instruction register

RS = 1 /R/W = 0 E=1 permiten enviar datos al LCD, escribiendo el data register.

Generalmente el valor del pin Enable se usa como un Toggle o pulso y este se dispara cuando ya tengo todos los datos de los demás pines (RS, RW y DB7 a DB0) estables y con valores correctos.

Instrucciones internas del LCD

El LCD posee instrucciones internas que sirven para desde limpiar la pantalla o elegir el ancho de los caracteres hasta encender o apagar el display, las mismas estan explicadas en la siguiente tabla:

El formato de estas instrucciones es:

Valor del pin RS,

Valor del pin /R/W,

Una cantidad de ceros y luego un 1 para identificar la instrucción en los pines DB7 a DB0,

El resto después del 1 que identifica la instrucción, son los parámetros de la misma.

Por ejemplo la instrucción Display on/off control que controla tres funciones de nuestros display está codificada de la siguiente forma:

RS/R/WDB7DB6DB5DB4DB3DB2DB1DB0
0000001DCB

El valor RS es cero para indicar que estamos escribiendo al instruction register del LCD

El valor /R/W es cero para indicar que es una escritura

La instrucción está indicada por los pines DB7 a DB3 y es 00001

El pin DB2 es un parámetro que indica si prender o apagar el display

El pin DB1 es un parámetro que indica si está prendido o no el cursor

El pin DB0 es un parámetro que indica si el cursor parpadea o no

Si quisiéramos que el display esté prendido, mostrando el cursor y que este no parpadee enviaríamos la siguiente secuencia

RS/R/WDB7DB6DB5DB4DB3DB2DB1DB0
0000001110

Inicializar nuestro LCD

Para poder empezar a enviar letras a nuestro LCD primero debemos inicializarlo, el datasheet nos indica una secuencia de inicialización que vamos a analizar.

El primer paso es conectar al suministro eléctrico de 5 volts a nuestro LCD, para esto no necesitamos ninguna instrucción solo conectar correctamente los cables.

El segundo paso es enviar la instrucción Function Set que en este caso elige la operación del display como 8 bits (usa todos los pines de DB7 a DB0), una sóla línea y formas de caracteres de 5×8 puntos.

RS/R/WDB7DB6DB5DB4DB3DB2DB1DB0
00001100N/AN/A

El tercer paso es enviar la instrucción Display on/off control donde indicamos prender el display, mostrar el curso y que este no parpadee.

RS/R/WDB7DB6DB5DB4DB3DB2DB1DB0
0000001110

El cuarto paso y último paso en la inicialización es enviar la instrucción Entry Mode Set para decidir cómo es que vamos a mostrar los caracteres en este caso decidimos que después de mostrar una letra corra el cursor un lugar a la derecha

RS/R/WDB7DB6DB5DB4DB3DB2DB1DB0
0000000110

Finalmente ya estamos listos para escribir nuestra primera letra.

Escribiendo una letra en nuestro LCD

Al tener preparado ya nuestro LCD para recibir nuestra primera letra, el mismo espera la información en código ascii explicitado en los pines del DB7 al DB0 por ejemplo, a continuación la tabla de caracteres del lcd.

Para escribir la letra H mayúscula por ejemplo vemos que la combinación es 0100 1000 com lo que deberemos enviar estos valores:

RS/R/WDB7DB6DB5DB4DB3DB2DB1DB0
10001001000

El valor RS es 1 para indicar que estamos escribiendo al data register del LCD

El valor /R/W es 0 para indicar que es una escritura

En los  pines DB7 a DB0 espera el dato a escribir en memoria y es 01001000

Cómo funciona el CIA en la Commodore 64

La Commodore 64 posee dos CIA 6526 pero ninguno está conectado a un LCD, utilizando al chip VIC2 y  un modulador de RF la Commodore puede comunicarse con monitores con su salida de video y a televisores con su salida RF, en una futura entrega vamos a desarrollar el funcionamiento del chip VIC2.

Estudio visual

Para poder estudiar visualmente como conectar el LCD a nuestros procesadores y CIAs/VIAs y como programarlo en assembler para mostrar un mensaje  les dejo esta video que complementa al artículo.

LCD con 6510/CIA y 6502/VIA – 6502 vs 6510 Parte 9

Referencias

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

VIDEOS

Video de la serie 6502 vs 6510 Parte 9 – LCD con 6510/CIA y 6502/VIA

LCD con 6510/CIA y 6502/VIA – 6502 vs 6510 Parte 9

Aquí tienen acceso a toda la serie:

6502 vs 6510 estudio detallado y comparación 

Aqui tienen acceso a todos los artículos publicados en Espacio Tec

Artículos en la serie C64 a Fondo

A continuación les dejo los links a los artículos anteriores de la serie

Introducción

Parte 1 – El módulo de reloj

Parte 2 – Pinout 6510 y 6502

Parte 3 – Codeando a Mano la Primera Instrucción de Código Máquina

Parte 4 – Primer Programa desde EEPROM

Parte 5 – I/O Pins del Procesador

Parte 6 – MOS 6503 Una Rareza

Parte 7 – VIA MOS 652 Interfaz con periféricos

Parte 8 – MOS 6526 CIA Interfaz con periféricos reloaded

PAPERS

W65C02S 8–bit Microprocessor 

6510 MICROPROCESSOR WITH I/O 

6502 Instruction Set 

​​HD44780U (LCD-II), (Dot Matrix Liquid Crystal Display Controller/Driver) 

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/carlinhocr/6502_vs_6510

C64 a Fondo – 6502 vs 6510 Parte 8 – MOS 6526 CIA Interfaz con periféricos reloaded

Hola, en esta entrega vamos a estudiar el MOS 6526 también conocido como CIA o complex adapter interface. Son los famosos chips que usa la Commodore 64 entre el procesador y el teclado, ports de usuario y ports de joystick (control port 1 y 2) entre otras conexiones.

¿Por qué necesito algo entre el cpu 6510 y un periférico?

Los procesadores tienen como características ser muy rápidos, estamos hablando de 1 millón de operaciones o ciclos de reloj por segundo en el caso del 6510. Un periférico como el datasette o la disquetera está en el orden de 300 bytes por segundo de transferencia. 

Si el procesador tuviera que esperar cada acceso de información disponible sería toda la computadora extremadamente lenta, por eso ponemos un chip en el medio para que medie entre  el procesador y los periféricos.

Este chip tiene como función recibir la información que el 6510 le envía, mantenerla disponible y pasarla al periférico cuando este la requiera; también recibir la información desde el periférico, mantenerla y avisar al procesador que tiene información disponible para él. De esta forma el bus de datos se mantiene libre hasta que el procesador tenga que comunicarse con el periférico.

El CIA – Complex Interface Adapter MOS 6526

Pinout

Los pines, VCC, RS0 a RS3, /RES, DB0 a DB7, PHI2, R/W, /IRQ presentan funcionamientos similares al VIA MOS 6522, pero primero vamos a dar detalles de funcionamiento de los pines presentes sólo en el CIA. Cuando en el diagrama un puerto comienza con una / leer como BARRA y significa que este pin es Active Low.

/PC y /FLAG se utilizan juntos para realizar un handshake entre el procesador y el CIA, también el pin /FLAG sólo ,puede utilizarse para recibir una señal de otro 6526 y quedar marcado como flag de interrupción encadenando ambos CIA. El pin  /PC es output y pin /FLAG es input siempre.

TOD este pin requiere una conexión externa de 60Hz o 50Hz con un nivel programable de TTL para mantener el reloj interno del CIA. 

CNT puede recibir pulsos para usar como un contador en Timer A o Timer B o ser output y escribir pulsos del timer A o B.

SP Es un pin de input o output donde los BITS del puerto serial del chip son escritos. El Serial Data Register se conecta con el Shift Register y cuando recibe un pulso en el pin CNT hace un shift out del Shift Register un bit a la vez en el pin SP, esto lo hace por 8 pulsos o 8 bits del registro momento en el cual se genera una interrupción para avisar que se pueden enviar más datos.

/CS el CIA posee un sólo pin que funciona como Chip Select, al estar en Low el chip responde a los datos en DB0 a DB7

Aquí repasamos los pines con funcionamiento similar al VIA

VSS y VCC/VDD son los pines de Ground y 5 Volts respectivamente

PA0 a PA7 Port Address A: son pines de datos bidireccionales (input y output) para el primer puerto de periféricos el A, el periférico se comunica con el 6526 por esos pines.

PB0 a PB7 Port Address B:  son pines de datos bidireccionales (input y output) para el segundo puerto de periféricos el B, el periférico se comunica con el 6526 por esos pines.

RS0 a RS3 son pines que permiten seleccionar qué registro interno del CIA se accede cuándo el procesador lee o escribe los puertos de datos D0 a D7

RESB puerto de reset, el 6526 necesita de un reset cuando el 6510 es prendido por primera vez o pasa por un reset, en este pin recibe la señal. (Active Low).

DB0 a DB7 Data Bus: en estos pines el procesador 6510 se comunica con el 6526 para leer o escribir datos y acceder a cualquiera de los registros internos del CIA.

PHI2 en este pin se recibe la señal de reloj que es la misma que tiene el 6510, al estar diseñado el 6526 para trabajar con estos procesadores todo el timing interno funciona coordinadamente con toda la línea 6500.

RWB en este pin específico, se indica si se está realizando una lectura (high) o una escritura (low) a los pines D0 a D7.

IRQB este pin cuando está en estado low crea una interrupción al procesador para que este tome información del puerto de periférico que corresponde.

Registros

Los registros varían para reflejar la diferencia en algunas funciones con respecto al VIA que ya estudiamos, el CIA posee 16 registros diferentes aquí los detalles de algunos de ellos.

Este chip posee 16 registros internos que permiten entre otras funciones:

  • Seleccionar si los pines del canal de datos son de input o output
  • Almacenar la información que entrega o recibe el procesador de modo de mantener el canal de datos vacío pero la información preservada.
  • Elegir si el CIA puede o no interrumpir al procesador cuando tiene información para entregar.

Si queremos usar por ejemplo los puertos del PB0 al PB7 como output para darle información al periférico debemos primero seleccionar el Data Direction Register B. El mismo se selecciona a través de los puertos RS0 a RS3 en este caso poniendo los pines con los valores de 0v o Ground (<0.4 volts) en caso de tener un 0 en la tabla siguiente o 5v en caso de tener un valor 1:

RS3= 0 RS2= 0 RS1= 1 RS0= 1

Esto corresponde con el valor 3 del Register Number (0011), nótese que es diferente al VIA que espera un valor de 0010 o sea de 2 para el data register B. 

Luego de esta selección el procesador debe escribir el valor deseado a través de los pines D0 a D7. Para poner un pin en OUTPUT el valor deberá ser 1 y en INPUT deberá ser cero. Si quisiera poner todos los pines como output el procesador escribirá 1111 1111.

Luego el procesador debe enviar la información que quiere mandar de output al periférico para esto se utiliza el registro Output Register. En nuestro caso usaremos el Output Register B para seleccionarlo en los pines RS3 a RS0 poner los siguientes valores:

RS3= 0 RS2= 0 RS1= 0 RS0= 1

Que corresponden al valor de registro uno (0001), fijense que es diferente al valor usado en el VIA que era de cero (0000).

Si quisiera tener como valores de Salida en PB7 a PB0 los valores 10101010 debería escribir estos mismos en el registros enviando esta información por los pines D7 a D0.

Estos puertos soportan input latching, que es una característica por la cual el puerto conserva el valor de input que recibió desde el periférico aunque el mismo no refresque la señal. Los datos se mantienen (aunque cambien los valores en los pines PB0 a PB7) mientras el flag CB1 de interrupción este set y se borran cuando este flag es desactivado. 

Handshaking

El CIA puede hacer handshaking usando el pin /PC como output y el /FLAG como input. /PC va a ir a valor cero o low por un ciclo de tiempo P  después de una lectura o escritura del Puerto B. Esta señal puede usarse para indicar “data ready” en el puerto B o “data accepted” en el mismo puerto dependiendo si se trató de una lectura o una escritura respectivamente.

Se puede hacer handshake en transferencia de 16 bits usando ambos puertos A y B siempre leyendo o escribiendo primero al puerto A.

Cualquier señal low al pin /FLAG va a disparar una interrupción del bit FLAG, se puede usar como un puerto genérico para recibir interrupciones o se puede conectar al puerto /PC de otro CIA para recibir el low de ese pin y saber que sucedió un handshake en el puerto B de ese otro CIA (CIA 2 por ejemplo)

Timers

Los timers A y B del CIA pueden ser arrancados y parados a voluntad usando el control register A o el B respectivamente.

Reloj

También como funciones adicionales presenta un reloj Time of Day (TOD). El TOD es un reloj de 24 horas con una graduación de una décima de segundo. Está organizado en 4 registros (que se seleccionan con los pines RS0 a RS3), décimas de segundo, segundos, minutos y horas. El mismo necesita en el pin TOD la conexión con un clock de 60 Hz o 50 Hz.

Contador

Otra función adicional es contar los pulsos del pin de reloj recibidos en PHI2 en TIMER A o TIMER B o los pulsos recibidos en el pin CNT.

Puerto Serial (Shift Register)

El CIA también posee un puerto serial sincrónico de 8 bits con un buffer de 8 bits. Un bit de control selecciona si se va a recibir un input o output. 

En modo input los datos recibidos en el pin SP se ingresan a un shift register cuando se recibe una transición de low a high en el pin CNT. Luego de 8 CNT los datos en el shift register se copian al Serial Data Register y una interrupción es generada. El procesador va a leer los 8 bits contenidos en el Serial Data Register a través de los pines D0 a D7.

En modo output el TIMER A se usa como generador de baudios. Los datos se envían por el pin de SP. La tasa máxima de transferencia en baudios es PHI2 dividido por 4. La transmisión va a empezar después de escribir al Serial Data Register, la señal del TIMER A aparece como pulsos de output en el pin CNT y los datos se escriben al Shift Register y luego saldrán uno a uno por el pin SP cuando un pulso CNT ocurra. Luego de 8 pulsos CNT se genera una interrupción para indicar que se pueden mandar más datos.

Si el procesador carga datos en el Serial Data Register antes de que se produzca la interrupción cuando esta se produzca y se transmitan los datos el Shift Register va a ser cargado automáticamente luego de la transmisión. Si el procesador se mantiene en forma constante 1 byte adelantado al consumo del Shift Register la transmisión será continua.

Los datos son transmitidos con el Most Significant Bit primero.

Cómo funciona el CIA en la Commodore 64 

La Commodore 64 posee dos CIA conocidos en el mother como U1 y U2, aquí podemos ver su conexión esquemática con otras partes de la computadora:

Esquema de conexión

Funciones CIA 1 (U1)

Las funciones de la CIA 1 incluyen el control del teclado, control de dos puertos de joystick, el control de los dispositivos tipo paddle (potenciómetro como para el Arkanoid) y la lectura de datos del datasette conectado al pin de Cassette RD.

Los 8 pines del Port A están seteados como input y conectados a los 8 conectores de columnas del conector de teclado CN1. Los pines del del Port B están seteados como inputs y conectados a los 8 conectores de filas del conector de teclado CN1 (20 pines machos). 

Las cuatros direcciones del Control Port 2 (JOYB0, JOYB1 ,JOYB2 y JOYB3) y el pin de botón BTNB están conectadas al puerto A. Las cuatros direcciones del Control Port 1 (JOYA0, JOYA1 ,JOYA2 y JOYA3) y el pin de botón BTNB están conectadas al puerto B. Si están cruzados los A y los B !

Funciones CIA 2 (U2)

Este posee dos funciones principales que son la conexión al puerto de Usuario, la conexión al bus serial de la commodore, conexión al puerto RS-232, la configuración del VIC (selección de la posición de la memoria interna) y el control de las interrupciones no enmascarables NMI.

El Port B está enteramente conectado al User Port al cuál también se conecta el pin 2 del Port A. El Port A en sus conexiones desde el pin PA7 a PA3 están dedicados al puerto serie.

Direccionamiento de Ambos CIA

Para acceder a los CIA vamos a usar los puerto RS3 a RS0 que están conectados a los puertos de direccionamiento A3 a A0 del 6510. De esta forma podemos acceder a los registros del CIA 1 con las direcciones de DC00 a DC0F y al CIA 2 con las direcciones de DD00 a DD0F

Los pines de chip enable de cada CIA (recordemos que esperan un resultado de 0 volts o LOW) están conectados a la PLA ( a través del pin 1 de I/O) y a dos chips 74LS239 que son decodificadores y entre ambos conectan la CIA, el VIC y el SID. 

El chip select no va a funcionar a menos que también se reciba la señal high del clock Phi2 al mismo tiempo.

El CIA responde al 6510 cuando el ciclo del reloj PHI2 va de low a high y a low nuevamente , esto toma aproximadamente unos 1000 nanosegundos. Durante ese tiempo el pin de CS (chip select) es mantenido en Low y si el pin de R/W es high ocurre una lectura y si es low ocurre una escritura, los pines RS reciben una dirección a un registro interno con el databus dando o recibiendo datos.

Teniendo en cuenta el diagrama de timing para que los datos sean válidos y por ejemplo puedan ser leídos, el 6510 tiene acceder al CIA mientras el pin CS esté en low, el registro interno sea direccionado por los register select RS3 a RS0, PHI2 debe estar high y los datos tienen que ser válidos (mantenidos correctamente en el tiempo).

En Resumen

Nuestros dos CIAs manejan teclado, joystick, timers, lectura de datasette, puerto de usuario, paddles y comunicaciones seriales.

El CIA en funcionamiento

Para ver visualmente como conectar un CIA al 6510 y setearlo como output y también en su función de uso de reloj les dejo como siempre un video en detalle de la serie. 

En este vamos a ver cómo se conectan los pines, que código máquina escribir para comunicarnos con los registros internos y un ejemplo de output.

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

Artículos en la serie C64 a Fondo

A continuación les dejo los links a los artículos anteriores de la serie

Introducción
Parte 1 – El módulo de reloj
Parte 2 – Pinout 6510 y 6502
Parte 3 – Codeando a Mano la Primera Instrucción de Código Máquina
Parte 4 – Primer Programa desde EEPROM
Parte 5 – I/O Pins del Procesador
Parte 6 – MOS 6503 Una Rareza
Parte 7 – VIA MOS 6522 Interfaz con periféricos

Referencias

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

Video para profundizar el saber sobre la CIA

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

CIA – C64-Wiki 

Libro Machine Language for the Commodore 64,128,  y otras Computadoras Commodore Anexo I

CIA 6526 Datasheet 

Commodore 64 clock using the CIA TOD clock (from “Compute!”) | Retro64 

C64 Schematic CIA 

vasm portable and retargetable assembler 

6502.org: Tutorials and Aids 

Reemplazo Moderno de la CIA

J-CIA64 6526/8521 Replacement for Commodore 64/128/1570/1571 – Retro 8bit Shop 

Referencias de velocidades de transferencias de periféricos:

C64/C128 I/O performances 

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/carlinhocr/6502_vs_6510