¡hola! en esta primera sección del {tutorial de uxn} vamos a hablar de los aspectos básicos de la computadora uxn llamada varvara, su paradigma de programación en un lenguaje llamado uxntal, su arquitectura, y por que puede que quieras aprender a programarla.
> El ecosistema de Uxn es un espacio de juego y exploración de computación personal, creado para desarrollar y utilizar pequeñas herramientas y juegos, y programable en su propio lenguaje ensamblador.
uxn es el núcleo de la computadora virtual (¿por el momento?) varvara. es lo suficientemente simple como para ser emulada por diversas plataformas de computación viejas y nuevas y ser seguida a mano.
el núcleo uxn está inspirado por las máquinas forth en que utiliza la recombinación de componentes simples para lograr soluciones apropiadas, y en que es una máquina basada en pila.
esto implica que está principalmente basada en interacciones con una pila "push down", dónde las operaciones son indicadas mediante la llamada notación postfija.
> Notación Polaca Inversa (NPR), también conocida como notación postfija polaca o simplemente notación postfija, es una notación matemática en la que los operadores siguen a sus operandos [...]
=> https://www.forth.com/starting-forth/1-forth-stacks-dictionary/#The_Stack_Forth8217s_Workspace_for_Arithmetic The Stack: Forth’s Workspace for Arithmetic (Inglés)
expresiones más complejas en notación infija, que requieren paréntesis o reglas de precedencia de operadores (y un sistema más complejo para codificarlas), pueden ser simplificadas mediante notación postfija.
también podemos escribirla de muchas otras maneras, por ejemplo:
``` 48 2 3 5 + / +
48 3 5 + 2 / +
```
¡asegúrate de que éstas expresiones funcionan y son equivalentes! sólo debes seguir estas reglas, leyendo de izquierda a derecha:
* si es un número, empujarlo a la pila
* si es un operador, tomar dos elementos de la parte superior de la pila, aplicar la operación, y empujar el resultado en la pila.
nota: en el caso de la división, los operandos siguen el mismo orden de izquierda a derecha. 3/2 sería escrito como:
``` 3 2 /
3 2 /
```
irás descubriendo cómo el uso de la pila puede ser muy poderoso ya que ahorra operandos y/o resultados intermedios sin necesidad de que asignemos explicitamente un espacio en memoria para ellos (por ejemplo, mediante el uso de "variables" en otros lenguajes de programación)
¡vamos a volver a la notación postfija y la pila muy pronto!
una de las cuestiones de programar una computadora a un nivel bajo de abstracción, como estaremos haciendo con uxn, es que tenemos que conocer y estar atentos a sus funcionamientos internos.
## 8-bits y hexadecimal
las palabras binarias de 8 bits, también conocidas como bytes, son los elementos básicos de codificación y manipulación de datos en uxn.
uxn puede manejar también palabras binarias de 16 bits (2 bytes), también conocidas como cortos, mediante la concatenación de dos bytes consecutivos. vamos a hablar más sobre esto en el segundo día de este tutorial.
los números en uxn son expresados utilizando el sistema hexadecimal (base 16), en el que cada dígito (nibble) va de 0 a 9 y luego de 'a' a 'f' (en minúscula).
* memoria de entrada/salida, con 256 bytes dividida en 16 secciones (o dispositivos) de 16 bytes cada uno: 8 bytes para entradas y 8 bytes para salidas.
cada byte en la memoria principal posee una dirección de 16 bits (2 bytes) de tamaño, mientras que cada byte en la memoria de entrada/salida posee una dirección de 8 bits (1 byte) de tamaño. ambos pueden ser accedidos aleatoriamente.
los primeros 256 bytes de la memoria principal constituyen una sección llamada página cero. esta sección puede ser referenciada por 8 bits (1 byte), y su propósito es almacenar datos durante el tiempo de ejecución de la máquina.
hay tres instrucciones diferentes para interactuar con cada uno de estos espacios de memoria.
la memoria principal almacena el programa a ser ejecutado, empezando en el byte 257 (dirección 0100 en hexadecimal). también puede almacenar datos.
las pilas no pueden ser accedidas aleatoriamente; la máquina uxn se ocupa de ellas.
## ciclo de instrucción
el cpu uxn lee un byte por vez de la memoria principal.
el contador de programa es una palabra de 16 bits que indica la dirección del próximo byte a leer. su valor inicial es la dirección 0100 en hexadecimal.
una vez que el cpu lee un byte, lo decodifica como instrucción y lo ejecuta.
la instrucción va a implicar normalmente un cambio en la(s) pila(s), y algunas veces también un cambio en el flujo normal del contador de programa: en lugar de apuntar al siguiente byte en memoria, puede ser apuntado a otro lado, "saltando" de un lugar en memoria a otro.
¿liste? para ejecutar varvara localmente y fuera de la red, tendríamos que obtener el ensamblador uxn (uxnasm) y el emulador (uxnemu) de su repositorio git:
para compilar uxnemu, necesitamos instalar la librería SDL2.
en una terminal en debian/ubuntu, correr:
``` sudo apt install libsdl2-dev
$ sudo apt install libsdl2-dev
```
on en guix:
``` guix install sdl2
$ guix install sdl2
```
## obtener y compilar uxn
obtengamos y compilemos uxnemu y uxnasm:
```
$ git clone https://git.sr.ht/~rabbits/uxn
$ cd uxn
$ ./build.sh
```
si todo fué bién, verás varios mensajes en la terminal y una pequeña ventana con el título uxn, y una aplicación demo: uxnemu está ahora corriendo una "rom" correspondiendo a esa aplicación.
puedes ajustar tu $PATH para tenerlos disponibles en todos lados.
la idea es que para correr un programa escrito en uxntal (el lenguaje ensamblador de uxn), primero tienes que ensamblarlo en una "rom", y luego puedes correr esta rom con el emulador.
por ejemplo, para correr {darena} que se encuentra en projects/examples/demos/ :
¡échale una mirada a los demos disponibles! (¡o no, y empecemos a programar los nuestros!)
# uxntal y un muy básico hola mundo
uxntal es el lenguaje ensamblador para la máquina uxn.
estuvimos hablando antes sobre el cpu uxn y las 32 instrucciones que sabe cómo ejecutar, cada una de ellas codificada como una sola palabra de 8 bits (byte).
ese lenguaje ensamblador uxntal implica que hay una relación uno a uno mapeando de una instrucción escrita en el lenguaje a una palabra de 8 bit correspondiente que el cpu puede interpretar.
por ejemplo, la instrucción ADD (suma) en uxntal es codificada como un byte con el valor 18 en hexadecimal, y corresponde al siguiente conjunto de de acciones: toma los dos elementos superiores de la pila, los suma, y empuja el resultado a la pila.
en sistemas de tipo forth podemos ver el siguiente tipo de notación para expresar los operandos que una instrucción toma de la pila, y el(los) resultado(s) que empuja devuelta a la pila:
esto significa que ADD toma el primer elemento desde arriba 'b', luego toma el siguiente primer elemento 'a', y empuja devuelta el resultado de sumar a+b.
ahora que estamos en eso, hay una instrucción complementaria, SUB (resta) (opcode 19), que toma los dos elementos superiores de la pila, los resta, y empuja a la pila el resultado:
nota que el orden de los operandos es similar al de la división que discutimos arriba cuando hablamos de notación postfija: es como si trasladáramos el operador de entre los operandos, hacia el final, después del segundo operando.
la primera línea es un comentario: los comentarios son encerrados entre paréntesis y debe haber un espacio entre cada paréntesis y el contenido. de manera similar a otros lenguajes de programación, los comentarios son ignorados por el ensamblador.
en la segunda linea ocurren varias cosas:
* |0100 : puede que recuerdes este número de antes - este es el valor inicial del contador de programa; la dirección del primer byte que el cpu lee. usamos esta notación para indicar que lo que esté escrito después será escrito en memoria después de esta dirección.
* LIT : esta aparece dos veces, y es una instrucción uxn con las siguientes acciones: empuja el siguiente valor en memoria a la pila, y hace que el contador de programa saltee ese byte.
* 68 : un número hexadecimal, que corresponde al código ascii del caracter 'h'
* 18 : un número hexadecimal, que corresponde a una dirección de entrada/salida: dispositivo 1 (consola), dirección 8.
* DEO : otra instrucción uxn, que podemos definir como lo siguiente: escribir el byte dado en la dirección de dispositivo dada, ambos tomados de la pila ( byte dirección -- )
leyendo el programa de izquierda a derecha, podemos ver el siguiente comportamiento:
* la instrucción LIT empuja el número 68 a la pila
* la instrucción LIT empuja el número 18 a la pila
* la instrucción DEO toma el elemento superior de la pila (18) y lo usa como dirección de dispositivo
mirando en la tabla de dispositivos de la referencia varvara, podemos ver que el dispositivo con la dirección 1 en el nibble superior es la consola (entrada y salida estandard), y que la columna con la dirección 8 corresponde a "escritura".
observe que los números literales que escribimos, 0100, 18 y 68, se escriben en hexadecimal utilizando 4 dígitos correspondientes a dos bytes, o 2 dígitos correspondientes a un byte.
en uxntal sólo podemos escribir números de 2 ó 4 dígitos hexadecimales. si, por ejemplo, sólo nos interesara escribir un único dígito hexadecimal, tendríamos que incluir un 0 a su izquierda.
nota que sólo puedes usar esta runa para escribir los contenidos de uno o dos bytes (dos o cuatro nibbles).
importante: recuerda que esta runa (y las otras con la palabra "literal" en su nombre) es un atajo para la instrucción LIT. esto implica que uxn empujará estos valores hacia abajo en la pila. esto puede prestarse a confusión en algunos casos :)
si sólo queremos tener un número específico en la memoria principal, sin empujarlo a la pila, simplemente escribiríamos el número tal cual, "crudo". esta es la forma en que lo hicimos en nuestros primeros programas de arriba.
incluso ahora que sabemos que #18 corresponde a empujar la dirección de dispositivo escribir en consola en la pila, por legibilidad y para asegurar nuestro código a futuro es una buena práctica asignar una serie de etiquetas que corresponderán a ese dispositivo y sus sub direcciones.
la runa @ nos permite definir etiquetas, y la runa & nos permite definir sub etiquetas.
podemos ver un pad absoluto a la dirección 10, que asigna lo siguiente a esa dirección. dado que la dirección consiste en un sólo byte, uxnasm asume que es para el espacio de memoria de entrada/salida o la página cero.
nada de esto sería traducido a código máquina, pero nos asiste al escribir código uxntal.
la runa para referirse a una dirección literal en la página cero o el espacio de direcciones de entrada/salida, es . (punto), y una / (barra) nos permite referirnos a una de sus sub etiquetas.
recuerda: al ser una runa de "dirección literal" va a agregar una instrucción LIT antes de la correspondiente dirección :)
podemos definir un macro llamado EMIT que tomará de la pila un byte correspondiente a un caracter, y lo imprimirá en la salida estandard. para esto, necesitamos la runa %, y llaves para la definición.
si miras en la tabla ascii, verás que el código hexadecimal 30 corresponde al dígito 0, 31 al dígito 1, siguiendo hasta el 39 que corresponde al dígito 9.
recordemos que el número tendría que ser escrito como un byte completo para que sea uxntal válido. si quisieras probar esta macro con, por ejemplo, el número 2, tendrías que escribirlo como 02:
en el {tutorial de uxn día 2} empezamos a explorar los aspectos visuales de la computadora varvara: ¡hablamos sobre los aspectos fundamentales del dispositivo de pantalla para que podamos empezar a dibujar en el!
¡sin embargo, te invito a tomar un pequeño descanzo antes de continuar! :)