Fig. 1: computadora de retransmisión BrainfuckPC en el fondo de su autorContinuando con la gloriosa tradición del resumen anual de mis proyectos informáticos más locos, les presento el tercer y
último artículo sobre el proyecto de computadora de retransmisión BrainfuckPC.
En series pasadas:
Después de diez años de soñar y reflexionar, más de dos años de trabajo y montaje sin prisas, puedo decir con confianza que el proyecto de computadora de retransmisión ha tenido lugar. A pesar del hecho de que la computadora es inútil desde un punto de vista práctico, y también se bloquea regularmente, se ha convertido en el punto de partida para los próximos, no menos locos proyectos cibernéticos.
Debajo del cortador están sonando bloques de relés, los cálculos de relés más rápidos del mundo, envolturas, indicadores de vacío y mucho más.
Lenguaje de programación Brainfuck
El lenguaje de programación brainfuck es quizás el lenguaje de programación esotérico más popular del mundo. Y al mismo tiempo, el verdadero
Turing es un completo atolladero . Solo 8 instrucciones en las que puedes escribir cualquier cosa, pero durante mucho tiempo.
Por ejemplo, me tomó tres días escribir y depurar el programa de división 355/113, que imprime 6 decimales en la terminal.
Figura 2: instrucciones del lenguaje brainfuckLa sintaxis completa del lenguaje se basa en la RAM para 30 mil celdas de memoria, con una capacidad de 8 bits.
- Con dos instrucciones + y -, cambiamos el valor en la celda de datos actual por uno hacia arriba o hacia abajo.
- Con dos instrucciones < y > cambiamos el puntero a la celda de datos actual por uno, moviéndonos de este modo hacia la izquierda o hacia la derecha a través de la memoria.
- Dos instrucciones más [ y ] - nos permiten organizar bucles. Todo dentro de los corchetes es el cuerpo del bucle. Bucles anidados permitidos. La lógica de la instrucción es simple: si el valor de la celda de datos actual no es igual a cero, realizaremos una iteración del ciclo, si es igual, saldremos del ciclo.
- Las dos últimas instrucciones . y , - le permite mostrar el valor de la celda actual en la consola, o ingresar su RAM. De este modo se logra la interactividad.
Sí, esto es más que suficiente para escribir cualquier programa. La existencia de compiladores de C
en brainfuck parece insinuar esto. Pero la densidad del código es inexistente. Para realizar operaciones simples, como agregar los valores de dos celdas de memoria, debe ejecutar cientos de instrucciones de brainfuck.
brainfuck ++
Intentemos aumentar la densidad del código al menos un poco. Al estudiar programas escritos en este idioma, puede prestar atención a que en su mayor parte consisten en secuencias de las mismas instrucciones
+ - <> . Esto lleva a muchos a la idea de doblar la secuencia de tales instrucciones en una sola y obtener un pequeño aumento en la productividad, que de un programa a otro puede ser de hasta decenas de por ciento, y dar un aumento múltiple en la velocidad. Por ejemplo, reemplazamos 10 operaciones incrementales con una operación +10. 20 operaciones de mover el puntero hacia la derecha a la operación> 20 y así sucesivamente.
Figura 3: instrucciones del lenguaje brainfuck ++De la teoría a la práctica.
Como comprenderá, uno no puede simplemente tomar y escribir en el lenguaje mental la operación Rb = Ra + Rb, donde Ra y Rb son células de memoria. Todo lo que podemos hacer es cambiar el contenido de una celda a una constante y verificar si es cero. Como resultado, para sumar los dos números, todo lo que nos queda es hacer +1 para la celda Rb y -1 para la celda Ra, hasta que el contenido de la celda Ra sea cero. Escribimos esto en forma de código en C:
void addMov(Memory &mem, uint16_t RbPos) { while (*mem) { mem += RbPos; (*mem)++; mem -= RbPos; (*mem)--; } }
Como resultado, el valor anterior aparecerá en la celda RbPos más lo que era en la dirección de origen. memoria de clase: un contenedor con celdas enteras de 65k. Su propiedad principal es que desbordar el valor del puntero lo devolverá al comienzo de la matriz. Como en mi hardware real.
La desventaja de la función descrita es la pérdida del valor original: se restablecerá a cero. Agregue otra variable Rc para guardarla:
void addCpy(Memory &mem, uint16_t RbPos, uint16_t RcPos) { while (*mem) { mem += RbPos; (*mem)++; mem -= RbPos; (*mem)--; mem += RcPos; (*mem)++; mem -= RcPos; } }
Como resultado, el término copiado se ubicará en la celda RcPos. Bueno, en el caso de que solía haber cero.
Dado que la notación que utilicé se parece mucho a brainfuck ++: simplemente reescribimos nuestra función en caracteres bfpp, tomando RbPos para 4 y RcPos para 5 como ejemplo:
[
>>>>
+
<<<<
-
>>>>>
+
<<<<<
]
Habiendo descrito todas las primitivas, puede comenzar a combinarlas en estructuras más complejas y obtener el programa de la funcionalidad necesaria. Como resultado, puede obtener un programa que divide 355 por 113 (o cualquier otro número uno encima del otro en 16 bits)
Programa de punto flotante class Memory { public: Memory() { memset(m_mem, 0, sizeof(m_mem)); memPtr = 0; } Memory& operator += (uint16_t ptr) { memPtr += ptr; return *this; } Memory& operator -= (uint16_t ptr) { memPtr -= ptr; return *this; } uint16_t& operator [] (uint16_t ptr) { return this->m_mem[ptr]; } Memory& operator = (uint16_t ptr) { memPtr = ptr; return *this; } uint16_t & operator * () { return m_mem[memPtr]; } Memory& operator ++ () { memPtr++; return *this; } Memory& operator -- () { memPtr--; return *this; } private: uint16_t memPtr; uint16_t m_mem[65536]; }; void calcPi() { Memory mem; *mem = 22; mem += 1; *mem = 7; while (*mem) { mem += 1; (*mem)++; mem -= 1; (*mem)--; mem += 2; (*mem)++; mem -= 2; }
Arquitectura de computadora de retransmisión
El elemento central del procesador de retransmisión es un sumador completo de 16 bits con transporte paralelo. Dos registros están conectados a él en la entrada. TMP es el registro temporal en el que se coloca el valor anterior, y CMD es el registro de comando en el que se almacenan la instrucción y la constante por la cual se cambiará el valor anterior.
Por lo tanto, puedo realizar operaciones optimizadas de brainfuck ++ y al mismo tiempo obtener saltos condicionales completos: Jump If Zero y Jump If Not Zero a cualquier lado del programa.
El resultado de la operación de suma se puede cargar a uno de los registros de contexto, AP, con el número de la celda de datos actual, o IP, con el número de la instrucción actual. Además, el resultado puede cargarse en la celda RAM actual, si se trata de instrucciones
+ y
-
Fig. 4: Arquitectura de la computadora de relé en funcionamiento. La etapa de carga de la nueva instrucción se reemplaza por la etapa de su ejecución.En primer lugar, necesitamos calcular el número de la siguiente instrucción, es decir, realizar una operación IP ++. Para hacer esto, se agrega uno al valor anterior del registro IP, el resultado se vuelve a escribir en el registro IP y la siguiente instrucción se carga en esta nueva dirección, en el registro CMD.
El segundo paso es la ejecución de la instrucción recién cargada. Si funciona con el sumador, el proceso de ejecución es similar al proceso de cargar una nueva instrucción: el valor anterior está en el registro temporal, agregamos la constante que se encuentra en los bits más bajos del registro CMD, escribimos el resultado nuevamente en el registro o en la celda de datos actual.
Por lo tanto, la instrucción se ejecuta en un tic del generador de reloj. En un frente descendente, cargamos la siguiente instrucción, en forma creciente, la ejecutamos.
no es un error, sino una característicaY aquí se reveló una característica. Después de encender la computadora, el primer frente del generador de reloj aumentará y, por lo tanto, tendremos que ejecutar la instrucción actual, que nadie ha cargado aún en el registro CMD, hay ceros.
Siga las instrucciones vacías y ... ¡haga IP ++!
Como resultado, la celda de memoria cero del programa contiene cero y nunca se ejecutará. La primera instrucción cargada desde la memoria será la instrucción en 0x0001.
Conjunto de instrucciones
Fig. 5: Conjunto de instrucciones de computadora del reléLas instrucciones son de 16 bits, donde los 4 bits de orden superior son responsables del tipo de instrucción y los 12 bits de orden inferior son la carga útil. En la mayoría de los casos esto es una constante.
- Instrucción NOP: ignorada.
- La instrucción CTRLIO es una instrucción especial cuyo comportamiento está codificado por la máscara de bits de carga útil. En primer lugar, implementa comandos para escribir en la consola y leer desde la consola (en modo síncrono o asíncrono). En segundo lugar, le permite configurar el modo de operación de 16 bits u 8 bits de la máquina. Y en tercer lugar, utilizando la instrucción CTRLIO.HALT, puede detener la máquina. Lo curioso es que los bits de la máscara
sin bloqueo Puede configurarlos al menos de una vez, pero el comportamiento de la máquina será indefinido. - La instrucción ADD es una operación de celda de datos. Cambia el valor en la celda por el valor de la constante. En este caso, el bit 12 es un bit con signo y se copia en los bits 13-15. Por lo tanto, la instrucción 0x2ffe se convierte en la operación * AP + = 0x0ffe, y la instrucción 0x3ffe se convierte en * AP + = 0xfffe. La operación de resta se reemplaza por la suma con un número negativo.
- Instrucción ADA: implementa la operación AP + = const y le permite navegar por la memoria.
- Las instrucciones JZ y JNZ son condicionales. Dependiendo de la bandera Z, puede saltar algunas instrucciones hacia adelante o hacia atrás, o permanecer en su lugar. Dependiendo del modo de funcionamiento de la máquina: 16 u 8 bits, el estado del indicador Z está determinado por el byte de datos menos significativo o por la palabra completa.
Especificaciones técnicas
BrainfuckPC es una computadora de 16 bits con un procesador de retransmisión de láminas, arquitectura Von Neumann y un conjunto de instrucciones Brainfuck ++
- Número total de relés: 578 piezas.
- El número total de elementos lógicos: 157 piezas.
- Ancho del bus de direcciones: 16 bits
- Direccionamiento: palabra por palabra
- RAM: 128 KB (64 Kslov)
- Ancho del bus de datos: 16 bits / 8 bits
- Frecuencia de reloj (actual / máxima): 25Hz / 40Hz
- Consumo de energía: 70W
- Dimensiones totales: 110kh650kh140mm
- Peso: 15kg
Inicialmente, se suponía que la computadora funcionaría a frecuencias de hasta 100 Hz ... Y esto, por un minuto, 4 octavas de piano. Desafortunadamente, las primeras pruebas mostraron que 40Hz es el techo, pero hay mucho de esto para el circuito de relé. tanto más cuando es necesario un reloj externo para aplicar dos pulsos por ciclo, debido a las peculiaridades del circuito de sincronización con una señal externa. 80Hz para la música ya es algo.
Composición de la computadora
Fig. 6: Componentes principales de la computadora del relé.Echemos un vistazo más de cerca a la computadora. Casi todo el volumen de la máquina está ocupado por unidades de procesador de relé. Por el momento, todo cabe en cinco bloques, pero hay espacio para seis, por lo que si realmente lo desea, más tarde se puede ampliar la funcionalidad del procesador.
Cada uno de estos bloques contiene 32 módulos, en cada módulo hay 3 o 4 relés de láminas RES55 y RES64. La fuente de alimentación de cada unidad es de 5V, 3A.
Fig. 7: Un conjunto de bloques y módulos de un procesador de relé, listo para su instalación en un marco.Cada módulo está unificado. 60x44mm, conector de 16 pines. Al ensamblar los bloques lógicos, inserté el módulo requerido en una ranura libre y actualicé las conexiones.
Fig. 8: Los módulos D-flip-flop se verifican para verificar su operatividad.Fila central: bloques sumadores y bloques de registro. Por encima y por debajo de ellos hay pestillos de 16 bits basados en RES43, que cambian el flujo de datos entre los bloques. Todos los datos están girando aquí.
La fila inferior es la fila de bloques lógicos del procesador. Ahora dos bloques están parcialmente llenos, pero si realmente lo desea, entonces modificar y expandir la funcionalidad debido al espacio libre es más que posible.
Fig. 9: El marco se ensambla a partir de una lámina de aluminio de 2 mm, por debajo del corte por láser. En la foto: marco ya soldado y preparado, listo para pintar.La parte superior es indicador. A la izquierda está el bloque de estado de la máquina: los indicadores basados en el IV-6 muestran el número de la celda de memoria actual y la noche de la instrucción actual, la instrucción en sí y el contador general de instrucciones ejecutadas. Esto último es muy útil, porque si el emulador, por ejemplo, dice que hasta el primer personaje de la consola debe ejecutar 30 mil instrucciones, el contador mostrará claramente dónde está la máquina ahora y cuándo terminará de contar.
Fig. 10: La vista final del área del indicador. En el proceso de fabricación.A la derecha está la tarjeta de memoria, el elemento más controvertido de la máquina. Aunque creo que la computadora todavía es de retransmisión, el procesador es definitivamente 100% de retransmisión. La periferia es más moderna. En particular, la RAM es un chip de memoria estática. Pero también lo hacen casi todos los creadores modernos de computadoras de retransmisión.
Fig.11: Programador. 16 líneas de dirección, 16 líneas de datos, líneas de alimentación, tierra y lectura de escritura. Total 36 contactos.Dado que la memoria de los programas y los datos se comparte, alguien o algo, cada vez que enciende la computadora, debe cargar el programa en la RAM. Esta tarea se asigna al programador. Por el momento, el programador se encuentra en la placa de memoria. Ahora tiene exactamente dos tareas.
- Descargue el programa en la RAM, porque cada vez que enciende la alimentación para hacerlo manualmente usando los interruptores de palanca es una holgazanería, aunque esta posibilidad está presente.
- Monitoree el estado de una determinada región de memoria y muéstrela en una matriz de LED de 32x16.
Esto no afecta al procesador, y ver en tiempo real lo que está sucediendo en la RAM es muy útil al depurar. Posteriormente, cuando el programador es externo, el panel LED servirá a uno de los módulos indicadores. Él ya conoce la dirección, queda por darle los datos de entrada.
Fig. 12: Diagrama de bloques de los periféricos del procesador.Entonces, en un futuro cercano, se verá el circuito de los periféricos del procesador. Solo los chips de memoria y los circuitos de coincidencia de señal con el circuito de relé permanecerán en la placa de memoria.
A través del conector de programación de 36 pines, puede conectar el programador y descargar el firmware a la computadora. Además del programador, que tiene el conversor de interfaz necesario, puede usar cualquier otro dispositivo. Al menos un lector de cinta perforada (por cierto, tengo uno, completo con un punzón y hasta un carrete de cinta), al menos un panel con interruptores de palanca.
Como resultado, la lógica del relé proporciona una determinada interfaz, y el convertidor de interfaz puede ser cualquiera. Por cierto, el conector de interfaz paralela será compatible con LPT ...
Demostración de trabajo y estado actual.
En primer lugar, el programa Hello World del artículo de Wikipedia fue ejecutado en la computadora.
El código fuente es el siguiente:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++
.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.
------.--------.>+.>.
Gracias al panel LED, puede ver claramente cómo cambian los datos:
Aunque a una frecuencia de 25 Hz, es difícil hacer un seguimiento de lo que está sucediendo en la RAM.
Una tarea más útil y práctica es calcular los signos del número Pi después del punto decimal. Está claro que las computadoras modernas han resuelto este problema
hasta 31.4 billones de caracteres . Pero el hecho mismo de que BrainfuckPC sea capaz de realizar esta operación sugiere que la computadora de retransmisión no es 100% inútil, sino solo 99.9.
En primer lugar, encontré un
algoritmo de cálculo listo para
usar escrito en brainfuck .
> ++++ (4 digits)
[<+>>>>>>>>++++++++++<<<<<<<-]>+++++[<+++++++++>-]+>>>>>>+[<<+++[>>[-<]<[>]<-]>>
[>+>]<[<]>]>[[->>>>+<<<<]>>>+++>-]<[<<<<]<<<<<<<<+[->>>>>>>>>>>>[<+[->>>>+<<<<]>
>>>>]<<<<[>>>>>[<<<<+>>>>-]<<<<<-[<<++++++++++>>-]>>>[<<[<+<<+>>>-]<[>+<-]<++<<+
>>>>>>-]<<[-]<<-<[->>+<-[>>>]>[[<+>-]>+>>]<<<<<]>[-]>+<<<-[>>+<<-]<]<<<<+>>>>>>>
>[-]>[<<<+>>>-]<<++++++++++<[->>+<-[>>>]>[[<+>-]>+>>]<<<<<]>[-]>+>[<<+<+>>>-]<<<
<+<+>>[-[-[-[-[-[-[-[-[-<->[-<+<->>]]]]]]]]]]<[+++++[<<<++++++++<++++++++>>>>-]<
<<<+<->>>>[>+<<<+++++++++<->>>-]<<<<<[>>+<<-]+<[->-<]>[>>.<<<<[+.[-]]>>-]>[>>.<<
-]>[-]>[-]>>>[>>[<<<<<<<<+>>>>>>>>-]<<-]]>>[-]<<<[-]<<<<<<<<]++++++++++.
Un problema: aunque se dice que este programa es mucho más rápido que otro programa, todavía calcula el siguiente carácter, es extremadamente lento.
Fig. 13: Tiempo necesario para generar N dígitos de Pi después del punto decimal.4 decimales tendrán que esperar casi una hora y media ...
Fig. 14: - Pi = 3! - ¡Qué grosero!Sin embargo, incluso dos caracteres no pudieron deducirse realmente; en cambio, la computadora declaró que Pi tenía 4 años y completó el trabajo.
Fig. 15: Él claramente sabe sobre el chiste de que según la ley marcial, pi puede llegar hasta cuatro.Decidí ir a otro lado y escribí una calculadora de fracciones
. Precisión - ¡6 decimales! Este es el resultado más preciso para fracciones con números de tamaño adecuado.
Después de tres noches de insomnio, escribí un programa sobre brainfuck, capaz de dividir dos números entre sí y generar el resultado con un punto flotante en la terminal. El veredicto del emulador es el siguiente: se requerirán 60 mil instrucciones. En el último, 10 mil por signo:
Fig. 16: El tiempo necesario para generar el siguiente lugar decimal al calcular la fracción.Qué tan rápido aparecerán los siguientes valores. Debo decir muy rápidamente en comparación con el programa anterior!
Pero la felicidad duró poco: la computadora comenzó a fallar en modo de 16 bits. Los diagnósticos mostraron que la tarjeta de memoria está engañando: establece constantemente el 13er bit. Haré una nueva tarjeta de memoria y todo pasará, pero por ahora me limitaré a una fracción
dos lugares decimales y modo de operación de 8 bits. ¡Lo más importante es que solo se deben seguir 1.600 instrucciones! A una frecuencia de 25 Hz, esto es poco más de un minuto.
Repetidamente y con un silbido, la computadora hace frente a la tarea.
Continuará ...
Ahora en la computadora puede ejecutar programas que no requieren la entrada del usuario. Hasta ahora, no he arruinado trivialmente la instrucción CTRLIO.CIN :) Y no voy a hacer esto pronto. La computadora está actualmente 98% completa. Y después de dos años de trabajo, se han acumulado muchos proyectos que están esperando el momento en que me ocuparé de ellos.
Por lo tanto, me cambio a otros proyectos
En primer lugar, esta es una computadora de tubo basada en decatrones conmutadores. Ya tengo un golpe y los decatrones mismos (aunque principalmente A101, la computadora saldrá aún más lenta que el relé, se necesita A103). Incluso 700 tubos de vacío ya están disponibles y mucho más ...

He preparado una memoria para ello:
16 piezas de cubos de memoria integrados para 128 palabras de 16 bits cada una. Interior: placas de ferrita de múltiples orificios, una especie de ramificación de memoria en anillos de ferrita.
Tampoco me olvido de la neumonía : mi amigo Anton está comprometido con la naturaleza. experimentos, pero más sobre eso la próxima vez.
... dejando las siguientes imperfecciones. Resolveré parte del problema para el festival a fines de mayo, parte - no:
- Una nueva tarjeta de memoria en la que solo se instalan chips de RAM y su arnés. Hay una placa de circuito para la tarjeta de memoria, la placa de circuito aún no se ha divorciado. En casa, será demasiado perezoso hacerlo (un bidireccional bastante denso), por lo tanto, incluiré esta placa en el orden cuando ordene tablas para un par de otros proyectos: un reloj mecánico en un relé y un neumoscopio.
- Junto con la nueva tarjeta de memoria, vendrán indicadores de marcación, hardware de visualización de terminal normal y lógica independiente para actualizar el panel LED.
- El programador, o más bien, el desarrollo de firmware para ello. En general, si tiene una tarjeta de memoria antigua, es redundante, pero como el conector de programación está disponible, ya puede cargar el programa con ella.
- La lógica del tiempo. Aquí estoy completamente flojo, porque literalmente hay 3 módulos lógicos. Definitivamente lo haré para el festival a fines de mayo.
- Instrucciones de lectura desde la consola. Está vinculado a la lógica del tiempo (en la operación sincrónica, la computadora debe detener la operación y esperar a que lleguen los datos).
- Envíe una solicitud al Libro Guinness de los Récords ... Como el procesador de relevos más rápido y al mismo tiempo el de lectura más lenta. 16 milliFlops no es para que usted "meta un abrigo de piel en sus calzoncillos" (de los comentarios en youtube).

Toda la documentación en la computadora de retransmisión está en el
repositorio en GitHub , y puede monitorear su estado en cualquier red social utilizando los enlaces en mi perfil.
UPD: me negué a participar en el festival.
Y sin embargo, del 25 al 26 de mayo, en Moscú, en el territorio de la Fábrica de Pan, se llevará a cabo el primer festival de producciones artesanales y antifactory de la cultura del bricolaje. Estaré presente allí con una computadora de retransmisión y también traeré un controlador de retransmisión para el filtrado automático . La entrada al evento es gratuita, por lo que estarás en Moscú en estos días; no pierdas la oportunidad de ver a mi monstruo de relevos en vivo. Si lo traigo sano y salvo, definitivamente lo demostraré en el trabajo.