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:
- Tomar el Opcode de la operación, Incrementar el Program Counter.
- Leer el byte de la próxima instrucción y descartarla.
- 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
- Tomar el Opcode de la operación, Incrementar el Program Counter.
- Leer el byte de la próxima instrucción y descartarla.
- Incrementar el stack pointer SP.
- Guardar el byte que se encuentra la posición de memoria $0100+SP y guardarlo en el Acumulador.
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:
- Tomar el Opcode de la operación, Incrementar el Program Counter.
- Leer el byte de la próxima instrucción y descartarla.
- 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
- Tomar el Opcode de la operación, Incrementar el Program Counter.
- Leer el byte de la próxima instrucción y descartarla.
- Incrementar el stack pointer SP.
- 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:
- Tomar el Opcode de la operación, Incrementar el Program Counter.
- Leer el low address byte de la dirección de salto, Incrementar el Program Counter (queda listo para la próxima instrucción).
- Operación interna Guardar el Address Low en ADL.
- 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.
- 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.
- Buscar el high byte del address al cual saltar ADH, grabar el stack pointer.
- 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.
- 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.
- Este valor $FD es cargado al registro B de la ALU mediante la señal ADL/ADD
- Se genera el valor $FF y se carga en el bus SB,
- Este valor $FF se carga del bus SB al bus registro A de la ALU mediante la señal SB/ADD.
- Finalmente con la señal SUMS se realiza la suma de ambos valores.
- 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.
- El stack pointer es cargado mediante la señal SB/S con lo que es actualizado con el valor decrementado
- 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
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
Load and Run from 6502 ASM (1/2) | C64 OS
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: