Atari Assembler – Primer programa

Ya vimos en la sección anterior que nuestro programa o subrutina mínima en código de máquina que sera llamada desde BASIC será:

PLA
RTS

Lo que nos falta es ver cómo hacemos un programa BASIC que incluya esta rutina y la ejecute.

El Atari tiene una memoria RAM de 64Kb en donde podríamos poner nuestro código en cualquier lugar.  Pero en la práctica, esta memoria está ocupada en diversas zonas por el código del sistema operativo, el código del interprete BASIC, el código de nuestro programa BASIC, las variables que usa el intérprete BASIC y el sistema operativo, etc, etc.  Lo que nos reduce el espacio disponible para nuestro propio código.

Una zona segura y simple de usar para programas chicos como los que veremos en este tutorial, es la que se encuentra entre las direcciones 1536 a la 1791.  Son 255 bytes que nadie estará ocupando.

A través de la instrucción POKE de BASIC, escribiremos nuestro código de máquina desde la dirección 1536 en adelante. Lo que necesitamos para ello es saber a qué bytes corresponde la secuencia PLA / RTS en código de máquina.

Sin más misterio y usando la tabla de conversión de códigos del 6502, el código de máquina equivalente a PLA y RTS es 104 y 96 respectivamente.  Puesto en memoria, nuestro código quedaría como sigue:

1536 104         PLA
1537  96         RTS

En donde la primera columna corresponde a las direcciones de memoria en donde iría nuestro código, la última columna indica nuestro código fuente en Assembler, y lo que está entre medio son los valores en código de máquina correspondientes a nuestro código en Asembler.

El código BASIC que incorpora nuestro código de máquina y lo ejecuta entonces sería:

10 POKE 1536, 104
20 POKE 1537, 96
30 PRINT USR(1536)

Las lineas 10 y 20 dejan el código de  máquina en la posición de memoria 1536 en adelante.  La linea 30 lo ejecuta y muestra el resultado en pantalla.  En este caso, el valor devuelto a BASIC es 1536

Primer Programa en Assembler
Primer Programa en Assembler

Este programa que no hace nada, sólo sirve para ver lo mínimo necesario que requiere un programa en Assembler para ser ejecutado desde BASIC.

En este ejemplo el número 1536 impreso es el valor de retorno de nuestra llamada al código de máquina.  Vemos que por omisión, BASIC retornó la misma dirección de ejecución de nuestro código.  Vamos a cambiar esto, pero antes debemos saber cómo el 6502 interpreta los números.

Números de 8 y 16 bits en el 6502

El 6502 es un procesador de 8 bit, esto quiere decir que internamente sus instrucciones están preparadas para manipular números de un máximo de 8 bits, esto es, números desde el 0 al 255.

Para trabajar con números mayores, se requieren más de 8 bits (1byte), por ejemplo con 16 bits (2 bytes) podemos trabajar con números desde el 0 al 65536, o sea 64KBytes.  No es de extrañar entonces que el 6502 al usar un bus de direcciones de 16 bits, sólo pueda acceder a 64KBytes de memoria a la vez.

Cuando se usa más de 1 byte se ordenan desde el byte menos significativo hasta el más significativo, de izquierda a derecha.  En terminos simples, si tenemos dos bytes L y H, el valor total representado por ellos es L + 256 * H.

Si te sientes perdido, piensa en la forma en que nuestra cultura maneja los números:  Tenemos dígitos con 10 valores posibles que van desde el 0 al 9, si queremos representar un valor que está fuera de este rango, agregamos un dígito y lo multiplicamos por 10 (el número de combinaciones).  Ponemos a la derecha los dígitos menos significativos (ej. unidades) y a la izquiera los más significativos (ej. decenas, centenas, etc.).

Entonces con 2 dígitos en nuestro sistema decimal tendremos:

Decenas, Unidades
Por ejemplo el número 92 son 9 decenas, 2 unidades, o bien 9*10 + 2*1

Con 3 dígitos en nuestro sistema decimal, tendremos:

Centenas, Decenas, Unidades
Por ejemplo 392 son 3 centenas, 9 decenas, dos unidades, o bien 3 * 100 + 9*10 + 2*1

Con 4 dígitos tendremos:

Miles, Centenas, Decenas, Unidades
Por ejemplo 4392 son 4 miles, 3 centenas, 9 decenas y dos unidades, o bien 4*1000 + 3*100 + 9*10 + 2*1

Y así sucecivamente.

Mientras tanto, el 6502 ordena dejando las cifras menos significativas al lado derecho.  Si el 6502 usara un sistema decimal como los humanos, lo veríamos así:

Unidades, Decenas, Centenas, Miles, etc.

Pero a diferencia de nuestra cultura que usa digitos con 10 combinaciones posibles, el 6502 usa bytes cada uno con 256 combinaciones posibles, y es por eso que en vez de multiplicar por 10, 100, 1000 etc, se multiplica por 256, 65.536, 16.777.216 etc.  También lo podemos ver así: Los multiplicadores para nuestro sistema decimal son 10, 10*10, 10*10*10, y en el 6502 tenemos 256, 256*256, 256*256*256…

Los amigos de las matemáticas ya se habrán dado cuenta de que en el sistema decimal tenemos los multiplicadores 10^0 para las unidades, 10^1 para las decenas 10^2 para las centenas, 10^3 para los miles, y en general 10^(n-1) para el dígito n.  Así mismo ocupando bytes tenemos 256^0 como multiplicador para el primer byte, 256^1 para el segundo, 256^2 para el tercero, 256^3 para el cuarto, etc.

Afortunadamente, en el 6502 se usan a lo más 2 bytes en la mayoría de los cálculos, así que si tenemos dos bytes sólo nos preocupamos de tomar el primero y sumarle el segundo multiplicado por 256.

Ejemplos de secuencias de bytes y su interpretación:

  0,   0 =   0 +   0*256 = 0
  1,   0 =   1 +   0*256 = 1
 30,   0 =  30 +   0*256 = 30
255,   0 = 255 +   0*256 = 255
  0,   1 =   0 +   1*256 = 256
  1,   1 =   1 +   1*256 = 257
  4,   1 =   4 +   1*256 = 260
 10,   2 =  10 +   2*256 = 522
250, 255 = 250 + 255*256 = 65530

Internamente, a un nivel más infimo, se aplica la misma lógica pero a nivel binario.  La unidad mímima es el bit que puede tomar dos estados posibles (encendido/apagado, verdadero/falso, etc).  Un bit sólo nos permite representar un máximo de dos combinaciones, para representar más de dos combinaciones agrupamos los bits, y cada uno lo multiplicamos por 2^0 el menos significativo, 2^1 el siguiente, 2^2 el tercero, etc.  Es por eso que con 8 bit podemos representar 256 combinaciones, número que se obtiene de 2^8.  Pretty cool uh?!

Retornando un valor a BASIC

Retomando nuestra subrutina de ejemplo, vamos a modificarla para retornar un valor a BASIC.  Este valor es de 16 bits por lo que necesitaremos 2 bytes para representarlo.  El interprete BASIC espera que el valor de retorno se lo dejemos almacenado en las direcciones 212 y 213.

A modo de ejemplo, vamos a hacer que nuestra subrutina le devuelva el valor 522 a BASIC.  Como pudimos ver en la sección anterior, esto significa que debemos escribir la secuencia 10, 2 en las direcciones 212 y 213 respectivamente.

Este sería el código en Assembler

PLA
LDA #10
STA 212
LDA #2
STA 213
RTS

El PLA y RTS que inician y terminan el código es lo que ya conocíamos.  El LDA y STA son nuevos.

LDA permite asignar un valor al registro A del 6502 (LoaD Acum), mientras que el STA copia el valor del registro A del 6502 a una dirección de memoria.

Agregando comentarios a este programa, tenemos:

PLA
LDA #10 ; A = 10
STA 212 ; Copia el valor de A en la dirección 212
LDA #2  ; A = 2
STA 213 ; Copia el valor de A en la dirección 213
RTS

Como pueden ver, STA es equivalente al POKE de BASIC, es como si ejecutáramos

POKE 212, 10
POKE 213, 2

Para incorporar esta nueva subrutina a nuestro programa en BASIC, primero debemos convertir el código fuente que esta en Assembler a código de máquina.  Así quedaría a partir de la dirección 1536

1536 104         PLA
1537 169  10     LDA #10
1539 133 212     STA 212
1541 169   2     LDA #2
1543 133 213     STA 213
1545 96          RTS

Si vemos cómo se convirtió el código fuente en código de máquina, es fácil concluir que 169 corresponde a LDA #, mientras que 133 corresponde a STA, mientras que los valores que se encuentran acompañando a la derecha corresponden a los parámetros (10, 212, 2, 213).

El nuevo código entonces será:

10 POKE 1536, 104
20 POKE 1537, 169
30 POKE 1538, 10
40 POKE 1539, 133
50 POKE 1540, 212
60 POKE 1541, 169
70 POKE 1542, 2
80 POKE 1543, 133
90 POKE 1544, 213
100 POKE 1545, 96
110 PRINT USR(1536)

Al ejecutar este programa, el resultado del USR impreso via print es como esperamos: 522

Captura de pantalla 2013-04-13 a la(s) 17.40.44Mejoras en la parte BASIC

Como podrán notar, ahora el programa es sólo un poco más largo, pero ya resulta tedioso escribir todos esos POKE’s.  Aprovechando la instrucción DATA de BASIC vamos a cargar todo el código a partir de lineas DATA.

10 TRAP 20: FOR I=0 TO 255: READ D: POKE 1536+I, D: next I
20 PRINT USR(1536)
30 DATA 104, 169, 10, 133, 212, 169, 2, 133, 213, 96

Esta nueva versión del programa BASIC lee todo desde DATA hasta que se acaban los valores.  El BASIC arroja un error cuando ya no hay más datos que leer, pero como pusimos TRAP 20, cuando  ocurra el error, BASIC pasará a la linea 20 y ejeutará el USR

Captura de pantalla 2013-04-13 a la(s) 18.05.26
De aquí en adelante, para probar nuevas rutinas sólo necesitaremos modificar la linea de DATA