Show HN: Una computadora programable hecha con compuertas NAND
(github.com/ArhanChaudhary)NAND: una computadora completa de 16 bits Turing-equivalente implementada en la web
- NAND es una computadora de 16 bits Turing-equivalente emulada en la web usando solo compuertas NAND y un reloj
- NAND tiene su propia CPU, lenguaje de máquina, ensamblador, lenguaje ensamblador, lenguaje de VM, traductor de VM, lenguaje de programación, compilador, IDE e interfaz de usuario
- NAND está basado en la plataforma Jack-VM-Hack descrita en el curso Nand to Tetris y en el libro relacionado
Ejemplos de programas
Average
- Un programa simple que recibe números de entrada y calcula el promedio
- Muestra flujo de control, operaciones aritméticas, I/O y asignación dinámica de memoria
Pong
- El juego Pong muestra el modelo orientado a objetos del lenguaje
- Mueve la paleta a izquierda y derecha con las flechas para rebotar la pelota
- Cada vez que la pelota rebota, la paleta se hace más pequeña, y el juego termina cuando la pelota cae debajo de la pantalla
2048
- El juego 2048 muestra recursión y lógica de aplicación compleja
- Mueve los números en una cuadrícula de 4x4 con las flechas
- Los números iguales se combinan cuando se juntan
- Llegar a la ficha 2048 significa ganar, pero puedes seguir acumulando más
- El juego termina cuando el tablero se llena y ya no se puede mover
Overflow
- Un programa que provoca intencionalmente un stack overflow con recursión infinita para escapar de la máquina virtual
- Aprovecha que no hay verificaciones en tiempo de ejecución para prevenir stack overflow
- Al ejecutarse, sigue imprimiendo el valor del stack pointer
- Cuando el stack llega al final del espacio de memoria previsto y se mete en la memoria del heap, la instrucción de impresión empieza a fallar de forma explosiva
SecretPassword
- Un programa que aprovecha que el runtime no previene stack smashing para llamar a una función inaccesible
- Requiere entender el layout de stack frames de NAND
- Permite que el usuario sobrescriba cualquier dirección de memoria con cualquier valor
- Si sobrescribes la dirección de retorno de una función con la dirección de otra función, puedes ejecutar código arbitrario
- Si ingresas una ubicación de memoria específica y el valor a sobrescribir, obtenidos inspeccionando manualmente direcciones del stack y el ensamblador, puedes ver esta idea en acción
GeneticAlgorithm
- Uno de los muchos componentes de NAND, y el que más tiempo tomó desarrollar
- Una simulación biológica que usa aprendizaje automático simple
- El "cerebro" de cada punto son vectores de aceleración, que evolucionan mediante selección natural hasta llegar al objetivo
- En cada generación, los puntos que "mueren" más cerca del objetivo tienen más probabilidad de ser elegidos como padres de la siguiente generación
- La reproducción muta los cerebros y simula de forma efectiva la evolución natural
- Debido a limitaciones de rendimiento, usa muchas técnicas de optimización para sortear las restricciones del hardware y hacerlo posible
Programar en Jack
- Lo más importante al programar en Jack es que no existe precedencia de operadores. Esa puede ser la razón por la que tu programa no funciona correctamente.
- Debes indicar la precedencia con paréntesis, como en
4 * 2 + 3→(4 * 2) + 3,if (~x & y)→if ((~x) & y)
Introducción a Jack
- NAND presume toda su propia pila tecnológica
- Jack es un lenguaje orientado a objetos de tipado débil. En pocas palabras, C con sintaxis de Java
- Aprendámoslo con un ejemplo
Tipos de datos personalizados
- Jack soporta tres tipos básicos:
int,char,boolean - Según lo necesites, puedes extenderlos con tipos de datos abstractos
- El conocimiento de programación orientada a objetos se aplica directamente
- En el ejemplo, la clase
Pointdefine un punto abstracto en el espacio - Las variables
fielddeclaran propiedades por instancia del tipo de dato - Se proporcionan funciones públicas
methodpara manipular puntos, de modo que el llamador pueda sumar puntos o calcular la distancia entre dos puntos - Todas las variables
fieldtienen alcance privado. Para acceder a ellas, deben exponerse mediante funcionesmethod - Por convención, las clases de datos definen un método
dispose - Consulta la sintaxis de llamadas a
functionymethod
Conversión de tipos débil
- Se dijo que Jack está muy inspirado en Java, pero eso es solo una fachada
- Java es un lenguaje de tipado fuerte y soporta características de tipos complejas como downcasting, polimorfismo y herencia
- En cambio, Jack en realidad solo soporta un único signed 16-bit integer
- Esa es la razón principal por la que Jack tiene tipado débil
- Por eso, al compilador de Jack no le importa si mezclas tipos distintos en asignaciones y operaciones
- Jack sigue ofreciendo un modelo orientado a objetos potente y funcional
- Esto te ayudará a entender cuándo y cómo debes hacer conversiones de tipo
Gestión manual de memoria
- Jack es un lenguaje con gestión manual de memoria
- Debes tener cuidado de liberar correctamente la memoria que ya no necesitas
- Si no lo haces, Jack OS asumirá que hay una fuga de memoria
- La mejor práctica es escribir un método
disposepara cada clase que encapsule correctamente la liberación - Si llamas este método cuando el objeto ya no se necesita, puedes evitar quedarte sin memoria de heap
- Si ya has usado otros lenguajes con gestión manual de memoria, como C, esto te resultará familiar
- La diferencia es que Jack OS almacena arreglos y cadenas en el heap, no en el stack
- Puedes ver
String.disposepara entender cómo escribir un métododispose
Comportamiento indefinido
Precedencia de operadores
- Es tan importante que se movió hacia arriba
Operadores menor y mayor que
- Las comparaciones de Jack
a > b,a < bparecen simples, pero no siempre son matemáticamente correctas - La máquina virtual las convierte en
a - b > 0. Peroa - bpuede desbordarse - ¿Cómo se evalúa
20000 > -20000? Se convierte en20000 - (-20000) > 0, es decir-25336 > 0, y dafalse - Pero
20000 > -10000se convierte en30000 > 0, es decirtrue - Si la diferencia entre los valores absolutos de
aybes mayor que 32767, entoncesa > bya < bfallan. Si no, funcionan bien - Esto no es un bug, sino una incompatibilidad con Nand to Tetris. No se corregirá por compatibilidad
-32768
- -32768 es el único número con la peculiar propiedad de que -(-32768) = -32768. Es un singleton sin contraparte positiva
- Esto puede causar errores lógicos inesperados
- Porque
-xse procesa internamente como~(x-1) - Si asignas
-32768ax, se cumplex-1 = ~x. Entonces~(~x)termina siendo igual ax - ¿Qué pasó? Como NAND es una máquina de 16 bits, al restarle 1 a -32768 el resultado es invertir sus bits
- Lo importante es manejar correctamente estos errores lógicos en el operador de negación
- Es responsabilidad del programador revisar el caso
-32768y manejarlo de forma adecuada
Llamadas a funciones con argumentos insuficientes
- Un comportamiento indefinido evidente que no necesita explicación
Conversión de tipos inapropiada
- Puedes convertir una variable a
Arraysin importar su tipo - Llamar a un método de instancia que no existe produce comportamiento indefinido
- El compilador no es lo bastante inteligente como para detectarlo
Stack overflow
- Consulta el programa Overflow
Modificar stack frames o registros internos
- Modificar stack frames o registros internos en las direcciones 256~2047 y 1~15 puede producir comportamiento indefinido
- Normalmente esto no es posible a menos que abuses de
Memory.pokeo de índices negativos de arreglos - Consulta el programa SecretPassword
Especificaciones de hardware
-
Hay una razón por la que el cómputo de 16 bits quedó obsoleto después de los años 70
-
En comparación con 32 bits o 64 bits, su capacidad de procesamiento y memoria es limitada, y no satisface las exigencias del software moderno
-
NAND no es la excepción
-
Comparado con una MacBook de 16GiB, NAND solo tiene 4KiB de RAM. Eso es apenas el 0.00002%
-
Aun así, alcanza para llevarnos a la luna
-
Jack OS reserva 14,336 direcciones de memoria del total de 4KiB para el heap. Es una cantidad absurdamente pequeña
-
Por eso es muy importante que las aplicaciones Jack liberen memoria de forma eficiente
-
Si tu plan es demasiado ambicioso, podrías quedarte sin memoria de heap y tener que reescribir por completo tus tipos de datos y tu lógica
-
Del total de 4KiB, 8,192 direcciones de memoria están reservadas para la pantalla
-
Cada bit de cada dirección se mapea linealmente a los píxeles de una pantalla de 512x256. Usa numeración de bits LSb 0
-
La dirección de memoria 24,576 está reservada para el teclado
-
Refleja el valor de código ASCII de la tecla presionada
-
Pero no debes acceder a esto directamente para manejar entrada del usuario. Debes usar la clase Keyboard de Jack OS y sus funciones relacionadas
-
El teclado de NAND reconoce ASCII y teclas especiales
-
Se reservan 240 direcciones de memoria para variables estáticas de clase y 1,792 para el stack global
-
A menos que hagas recursión profunda, este límite no debería ser un problema
1 comentarios
Opiniones de Hacker News