Un sorprendente minicomputador multi-usuario de oficina

Este artículo es la versión en castellano, actualizada, del original en inglés.

La mayoría de los usuarios de Windows saben que el procesador que lo ejecuta es un procesador Intel x86 o compatible, que proviene de una línea que comenzó con el i8086, el primero de los microprocesadores de 16 bits de Intel, de allí la i inicial. Le sucedieron el i80186, el i80286, etc. Eso es lo que representa la "x" en "x86", el dígito central que va aumentando en número, que eliminaron cuando decidieron que ya no podían seguir con ese sistema de numeración y empezaron a darles nombres, algo que comenzó con el Intel Pentium, que de otro modo habría sido un i80586.

Aunque más tarde pasaron a arquitecturas de 32 y 64 bits, el conjunto de instrucciones básicas, los códigos binarios reales que le indican al procesador qué hacer, se derivan del procesador i8086 original.

El i8086 no fue el primer procesador de Intel. De hecho, comenzó con el i4004 , un procesador de 4 bits. La cantidad de bits de un procesador es el tamaño del conjunto de bits con los que puede operar simultáneamente. Con 4 bits, se tienen 2⁴ símbolos, es decir, 16 combinaciones de bits diferentes que se pueden usar, por ejemplo, para representar cada uno de los 10 dígitos decimales que usamos habitualmente, con 6 símbolos de sobra. Este chip fue diseñado para calculadoras de bolsillo, con 4 bits bastaba. Poco después, le siguió el i4040 en la misma línea.

Luego llegaron los procesadores de 8 bits, que contaban con 2⁸, es decir, 256 símbolos diferentes, suficientes para representar números, letras mayúsculas y minúsculas, puntuación y aún quedaban muchos símbolos de reserva. La codificación EBCDIC de IBM usa 8 bits y la US-ASCII usa 7 bits, por lo que ambos encajan en 8 bits.

Intel hizo un primer intento con un procesador de 8 bits con un chip que llamaron i8008 destinado a una terminal ASCII inteligente, la Datapoint 2200, seguido por el i8080 que dominó el mundo de la microinformática de 8 bits durante varios años junto con sus derivados, como el Zilog Z80 que estaba en el corazón del Tandy / Radio Shack TRS-80 o del Sinclair ZX80, todos los cuales compitieron con la familia de procesadores 6502 populares en Amiga, Commodore y Apple.

Fue en aquellos tiempos, cuando un pequeño microprocesador de 8 bits apenas podía manejar una sola microcomputadora, que una empresa de Ann Arbor, Michigan, Sycor Inc., anunció un sistema capaz de admitir hasta 8 usuarios en una sola máquina: el Sycor 445. Sycor desapareció hace mucho tiempo, vendida a la canadiense Northern Telecom, que continuó fabricando esos sistemas bajo su marca como NT 445, con colores ligeramente diferentes en la carcasa, y posteriormente desarrolló el NT 585. Les perdí la pista después de eso; Northern Telecom, luego rebautizada Nortel, eventualmente quebró.

Creo que Sycor vendía principalmente sus sistemas anteriores a través de otras marcas más reconocidas en el mercado, al menos en el extranjero. Sus primeras máquinas llegaron a la Argentina a través de la filial local de Olivetti de Italia, razón por la cual me involucré con ellos. Posteriormente, Olivetti quiso desarrollar sus propios sistemas en este rango y abandonó los de Sycor antes de tener listo el propio. Sin embargo, como se habían vendido muchas máquinas Sycor en Argentina, quienes teníamos experiencia con esos sistemas nos pasamos a otras empresas que se encargaron del soporte técnico de los sistemas instalados e incluso vendieron los nuevos directamente de Sycor y, posteriormente, de Northern Telecom.

La 445, como la llamábamos, era una máquina maravillosa. El gabinete llegaba hasta la cintura, unos 40cm de ancho y un metro de profundidad. Su diseño simple y elegante encajaba en cualquier oficina, siendo silenciosa y sin requisitos de instalación especiales como aire acondicionado. Podía manejar hasta ocho terminales, cada una a una distancia de hasta 600m, lo que permitía atender no solo a las oficinas administrativas, sino también a las terminales de la fábrica o las del almacén. Varios 445 se podían interconectar entre sí formando una red de área local LAN, compartiendo recursos entre todas ellas. Conocí un cliente que tenía 5 unidades conectadas entre sí con las terminales por toda la empresa y varias unidades de discos externos removibles de 300MB cada uno, un verdadero centro de cómputos con sólo 5 445.

Internamente, contaba con un microprocesador Intel i8080 con hasta 256kB de memoria y un disco duro de 10MB en un único plato de 14 pulgadas o 36cm de diámetro. Se podía añadir mucho más almacenamiento externo. Recordemos que eran finales de los 70 y las capacidades de memoria y disco estaban en esos rangos: kilobytes y megabytes, nada de gigas ni teras. Nadie podía imaginar semejante capacidad de almacenamiento en aquella época.

El i8080 no era realmente un microprocesador de un solo chip, sino un conjunto de tres chips. Todos los diseños de Intel desde el i4004 habían sido multi-chip. La CPU necesitaba un par de chips de soporte, un generador de reloj y un controlador de bus. También requería fuentes de alimentación de más y menos 5 voltios y de más 12V. Sus sucesores, como el propio i8085 de Intel o el Z80, prescindieron de todo esto, lo que resultó en diseños mucho más compactos.

Circuitería adicional

El i8080 podía gestionar hasta 216, es decir, 64kB de memoria. Los ingenieros de Sycor hicieron un excelente trabajo al ampliar esta capacidad a 256kB mediante el mapeo de memoria mediante un circuito simple e increíblemente elegante, creado con circuitos integrados de pequeña escala (SSI). Cualquier combinación de bloques de 2kB de esos 256kB de memoria principal podía mapearse en los 64kB del i8080. A medida que el sistema operativo cambiaba de tarea, se encargaba de asignar a cada tarea sus bloques de memoria correspondientes.

Todo ese circuito, y algunos más que mencionaré en breve, residían en una placa de circuito impreso (PCB) del tamaño aproximado de un disquete de los de entonces, es decir, uno de 8", ya que los más pequeños de 5" ¼ aún no habían llegado. Ambos eran flexibles, a diferencia de los posteriores de 3,5", que eran rígidos.

El 445 tenía una placa base que recorría todo el chasis y que albergaba y conectaba a todas las otras. A diferencia de otros sistemas de bus, donde todas las ranuras de la placa base son prácticamente iguales (todos los pines 1 están conectados entre sí, al igual que los pines 2, y así sucesivamente, de modo que cualquier placa puede conectarse en cualquier lugar), cada ranura, o pequeño conjunto de ellas, estaba dedicada a una función específica. Por lo tanto, había dos ranuras reservadas para las CPUs, cuatro para las placas de memoria RAM, cuatro para las terminales, una para la placa controladora del disco interno, una para el cartucho de cinta magnética, y así sucesivamente con todos los periféricos. Estas eran las opciones básicas del sistema. Otras placas podían gestionar unidades de disco externas, unidades de cinta de carrete abierto de media pulgada, redes, comunicación remota y varios dispositivos externos más.

Placa base de un PDP-8I con cableado enrollado o *wire-wrapped*

La placa base solo tenía las trazas de cobre para proveer alimentación y tierra que eran comunes a todas las placas y servía de soporte a una serie de zócalos donde enchufar las placas funcionales y en cuyo reverso tenía postes para enrollar cable, que era la forma habitual de construir sistemas en aquella época. Estos postes se conectaban entre sí con innumerables cables, como se ve en la imagen superior. A medida que se disponía de más periféricos y se rediseñaban otros, algunas ranuras podían recablearse. Esto permitía una gran flexibilidad y crecimiento, ya que el diseño del sistema podía modificarse con relativa facilidad, pero recablearlos era realmente peligroso, ya que cualquier error podía ser muy difícil de identificar en semejante maraña de cables. Un pequeño descuido con el alicate podía llevar un par de días averiguar qué fallaba. Afortunadamente, esos cambios de diseño implicaban muy pocos cables que tuvieran que cortarse y reemplazarse. Nunca recableábamos una placa base en su totalidad. Aun así, solíamos hacerlo dos personas: una cortando y enrollando cables, la otra leyendo las instrucciones, supervisando a la primera y tildando las casillas en la hoja de trabajo.

Originalmente, el sistema se diseñó con dos CPU, ambas i8080. Una operaría como CPU principal mientras la otra se encargaría del manejo de todos los periféricos. Si alguna vez salió de fábrica un sistema con doble CPU no lo sé pero al menos estoy seguro de que nunca llegó a Argentina. Pero, antes de explicar cómo solucionaron esto, permítanme explicar cómo afectó esto al diseño de la placa base de la CPU.

La placa de la CPU, con su i8080 y sus circuitos auxiliares, y el mapeador de memoria recibieron algunas mejoras. Con dos CPU en el sistema, como se había planeado originalmente, se habría producido un cuello de botella al intentar acceder a la memoria, ya que esta se compartía entre ambas. Por lo tanto, añadieron dos funciones adicionales a la placa de la CPU. No estoy completamente seguro de mi descripción de las mismas, ya que me baso en recuerdos de hace varias décadas y no conservo ninguna copia de los manuales, pero la funcionalidad estaba ahí, aunque quizás no exactamente como la describo.

Almacenamiento en memoria caché

Todas las lecturas de datos se realizaban en palabras de 16 bits. Aunque el i8080 era un procesador de 8 bits de ancho de palabra, se añadieron circuitos adicionales para que, al acceder a un byte, también se leyera el siguiente. El byte solicitado pasaba directamente al chip de la CPU y el siguiente se almacenaba en un simple latch, una memoria de un byte por así decirlo. No recuerdo cómo se gestionaban las escrituras, supongo que iban directas de la CPU a la memoria sin ninguna optimización. Como la cantidad de lecturas es mucho mayor que de escrituras, se pueden realizar escrituras deficientes y aun así tener una ventaja optimizando solamente las lecturas.

Dado que la mayoría de las operaciones de datos suelen implicar el acceso a múltiples bytes consecutivos de la memoria, como la lectura de caracteres consecutivos de una cadena, era lógico tener la placa base cableada para 16 bits de datos y que la placa de la CPU leyera dos bytes a la vez. Muchos compiladores pueden configurarse para almacenar variables a partir de una ubicación de memoria par de modo que el código compilado quede optimizado para esta transferencia de datos de 16 bits.

Esto es importante porque, con dos CPU, no se desea que cada una espere constantemente a que la otra realice sus búsquedas de memoria. Por lo tanto, si cada CPU puede leer dos bytes en una sola operación, le da tiempo a la otra para hacer lo mismo alternando turnos.

Precarga de instrucciones

El otro truco que usaron fue la precarga de instrucciones. Si se intenta optimizar la transferencia de datos a través de un bus compartido, la precarga de bloques de códigos de instrucción es una excelente opción, debido a las características comunes de los programas cuando se ejecutan.

La mayoría de los programas se componen de secuencias más o menos largas de códigos de instrucción almacenados en memoria consecutivamente. Los programas son como tramos más o menos largos de caminos sin bifurcaciones con intersecciones ocasionales. Las bifurcaciones son códigos de instrucción que indican a la CPU que continúe la ejecución por otro lado. Afortunadamente, no ocurren con tanta frecuencia; menos de 1 de cada 5 instrucciones es una bifurcación. Y muchas bifurcaciones son condicionales; es decir, si la condición falla, el programa continúa con la instrucción siguiente como si nada. Además, en un i8080, muchos códigos de instrucción requieren dos y algunas hasta tres bytes, por lo que, estadísticamente, si se lee un byte que contiene un código de instrucción de la memoria, es probable que se deba leer el siguiente de la ubicación inmediatamente posterior, y el siguiente, y así sucesivamente. De vez en cuando, se encontrará y se ejecutará una bifurcación, lo que puede obligar a descartar lo precargado, pero no importa mucho, la mayoría de las veces, en promedio, 9 de cada 10, lo precargado se aprovecha.

Además, ningún programa que funcione correctamente se modifica a sí mismo, por lo que se puede asumir que la memoria donde reside el código es de solo lectura. Uno de los problemas del almacenamiento en caché de datos es que lo que se acaba de almacenar podría ser modificado por otras CPU del sistema y la copia en caché podría dejar de reflejar el valor actual en la memoria. Esto no ocurre cuando el código es inmutable. Por lo tanto, si se precargan varias instrucciones, se puede asumir con seguridad que son válidas y lo seguirán siendo.

Afortunadamente, el chip i8080 tenía un pin que indicaba si lo que estaba leyendo era un dato para procesar o un código de instrucción para ejecutar, así se puede saber cuándo lo que quiere leer es código y, por ende, precargarlo.

¿Por qué no hubo una segunda CPU?

Aunque la placa base tenía una ranura para una segunda tarjeta de CPU, no tengo constancia de que se vendiera ninguna gracias a que salió el i8085. Este era un derivado del i8080 original, pero más adecuado como microcontrolador que como CPU principal. Un microcontrolador es lo que se utiliza para gestionar hardware externo, como unidades de disco o cinta, puertos de impresora paralelos o puertos de comunicación serie y otros dispositivos. El i8085 necesitaba solamente 5V y tenía su propio generador de clock incorporado, reduciendo los requerimientos externos y disponía de más pines dedicados a recibir señales directamente de los periféricos para responder con mayor prontitud. Por lo tanto, en lugar de depender de una segunda placa de CPU con un i8080 para gestionar todas las entradas/salidas (E/S) y que los controladores fueran simples, Sycor decidió instalar un i8085 en cada placa controladora de E/S y dotar a cada una de ellas de inteligencia (al menos algunas de ellas).

Cuando se requería una operación de E/S, el sistema operativo escribía lo que se denominaba un Bloque de Control de Dispositivo (DCB por sus iniciales en inglés) en la memoria principal. El DCB contenía información sobre qué hacer y dónde ubicar los datos a intercambiar. A continuación, le enviaba una señal al controlador de E/S avisándole que tenía este nuevo DCB esperando a ser procesado en tal o cual dirección y lo dejaba realizar su trabajo, mientras la CPU principal continuaba con otra cosa, posiblemente pasando a ejecutar otro programa o a atender a otro usuario. Una vez finalizada la operación, el controlador de E/S informaba al sistema operativo que esta se había completado, habiendo registrado los resultados en el DCB o donde este hubiera indicado.

De hecho, el DCB tenía un campo que apuntaba al siguiente DCB para el mismo tipo de dispositivo. Por lo tanto, una vez que un controlador terminaba con un DCB, podía pasar directamente al siguiente DCB en la cadena sin distraer a la CPU principal. Asimismo, el sistema operativo no necesitaba esperar a que un controlador de E/S procesara un DCB para indicarle qué hacer a continuación; simplemente encadenaba DCBs con todas las solicitudes de E/S que necesitaba y dejaba que los controladores de E/S las procesaran a su propio ritmo.

Terminales

El sistema podía gestionar hasta ocho terminales con pantalla y teclado -en esa época no había mouse ni interfaz gráfica- cada una a una distancia de hasta 600 metros y lo hacía de la forma más ingeniosa. ¿Recuerdan que mencioné que la CPU tenía un mapeador de memoria que podía asignar cualquier bloque de 2kB a los 64kB que el i8080 podía manejar? Uno de esos bloques de memoria de 2kB era la memoria de pantalla, una para cada terminal. Esta memoria no se encontraba junto a la memoria principal sino en cada placa controladora de terminal pues, mientras que la CPU principal accedía a esta memoria ocasionalmente, el controlador de pantalla la utilizaba continuamente, por lo que era lógico tenerla dentro del propio controlador.

En términos informáticos 2k significa en realidad 2048 bytes no 2000 como cabría esperar del prefijo kilo, simplemente porque 2048 es un número redondo en binario: es 210 o un 1 seguido de 10 ceros en binario. El controlador de pantalla utilizaba esta memoria para diversos fines. Con 25 líneas de 80 caracteres cada una (24 de texto más la línea de estado inferior), 2000 bytes se utilizaban para los caracteres que se mostraban en la pantalla de vídeo. Los 48 bytes restantes se utilizaban para diversos fines, como la posición del cursor, el estado de la tecla Bloq Mayús o si se pulsaba la tecla Mayús. El resto se reservaba para el búfer del teclado, una cola de corta duración para almacenar las pulsaciones de teclas hasta que el programa en ejecución pudiera procesarlas.

Lo más ingenioso era que la terminal era un monitor de televisión analógico completamente tonto. Un par de cables simplemente transmitían la señal de televisión normal, una línea de barrido a la vez, con su correspondiente pulso de sincronización horizontal (HSync) y el pulso de sincronización vertical (VSync) como un aparato de TV analógico cualquiera. Incluso tenía peor resolución que una señal de televisión convencional y no mostraba tonos de gris; era simplemente píxel encendido o apagado. Por eso las terminales de pantalla podían estar tan lejos: eran poco más que simples monitores de televisión.

La señal de TV se generaba mediante un barrido continuo por los 2kB de la memoria de la pantalla convirtiendo cada carácter en el patrón de píxeles a mostrar al pasar por una memoria ROM con la única fuente disponible, varias veces por segundo. Por eso, la memoria de la pantalla se encontraba en la tarjeta controladora del terminal y no formaba parte de la memoria principal. La CPU principal solo necesitaba acceder a esta memoria cuando algo cambiaba en la pantalla, lo cual era relativamente poco frecuente. El controlador de vídeo lo hacía continuamente.

El truco estaba en el teclado. Los teclados suelen tener una matriz de cables con varias columnas y filas, con teclas en las intersecciones. El controlador del teclado activaba, digamos, una columna y leía todas las filas una a una para esa columna, luego activaba otra columna y volvía a leer todas las filas y así repetidamente. Al pulsar una tecla, se conectaba un cable de columna con un cable de fila; de esta manera, cuando el cable de columna estaba activo, el cable de fila correspondiente también lo estaba.

Este teclado tenía un contador simple que se reiniciaba con la señal VSync del monitor y se incrementaba en uno por cada señal HSync. Cada valor del conteo correspondía a una columna a activar y una fila a leer. La placa controladora de pantalla tenía el mismo tipo de contador, contando en paralelo al del terminal, utilizando las mismas señales VSync y HSync. El teclado enviaba la señal de tecla pulsada al controlador, el cual, al llevar el mismo conteo en sincronía con el teclado, sabía qué coordenada de la matriz estaba cortocircuitada y a qué carácter correspondía.

Este circuito tan simple permitía que las terminales estuvieran a una distancia de hasta 600 metros y aún así responder como terminales locales, lo que era toda una hazaña en aquellos días.

CP/M y MP/M

Aprendí muchos de estos detalles gracias a un proyecto personal. El 445 ejecutaba su propio sistema operativo propietario, como la mayoría de las computadoras de la época. Contaba con un compilador COBOL, un intérprete BASIC muy primitivo (experimental) y un lenguaje propietario para describir y validar las pantallas de entrada de datos. Esto me parecía insuficiente. CP/M era el sistema operativo más popular en aquel entonces para computadoras personales de 8 bits y contaba con todo tipo de software de aplicación, como procesadores de texto, hojas de cálculo y compiladores e intérpretes para muchos lenguajes de programación además de COBOL. Además, podía personalizarse para funcionar en cualquier máquina con un microprocesador compatible con i8080.

Como éramos el único distribuidor de Sycor del país, siempre teníamos algunos 445 a mano, ya sea para desarrollar software para clientes o en el taller; algunos en funcionamiento, otros como repuestos o bancos de pruebas. Así que, si alguno estaba disponible, podía jugar con él.

Para personalizar CP/M, era necesario escribir, generalmente en ensamblador para i8080, los controladores para la terminal y la unidad de disco de la máquina. Como en el 445 el controlador de disco era inteligente, solo tenía que escribir en memoria los DCB que mencioné antes y el controlador de disco se encargaría de todo. Esto me evitó la molestia de gestionar las complejidades del hardware del disco. ¡Y funcionó! Pude cargar CP/M en el disco de mi máquina y arrancarla directamente en CP/M. Pero ese era solo el primer paso.

CP/M era un sistema operativo para un solo usuario y solo podía gestionar los 64kB de memoria básicos a los que el i8080 podía acceder por sí solo. Tenía una máquina capaz de gestionar hasta 8 usuarios y 256kB de memoria. Por eso mi objetivo era el MP/M, que era la versión multiusuario de CP/M, que también se podía personalizar para cualquier máquina compatible con CP/M y que tuviera mapeo de memoria para poder darle a cada usuario su buena cuota de memoria.

Para hacer esto, era necesario tener una máquina que ejecutara CP/M, que yo ya tenía, y luego agregar el código para manejar el mapeador de memoria para así asignar cualquier bloque de 2kB de esos 256kB de memoria principal más los 2kB de cada pantalla a los 64kB dedicados para cada usuario.

Desafortunadamente, nunca terminé esa parte y nunca conseguí que el 445 funcionara con MP/M. Surgió un gran proyecto y todos los recursos de la empresa, tanto de la máquina con la que estaba trabajando como de mis compañeros y yo, tuvimos que trabajar en él. Para cuando terminó el proyecto, me trasladaron a otra área y rara vez volví a tener un 445 para mi. Y, de todas formas, había sido una apuesta perdida de antemano, ya que, para entonces, el IBM PC se estaba volviendo popular y una máquina MP/M multiusuario de 8 bits ya no era atractiva. Además, estaba trabajando con una familia mucho más grande de minicomputadoras y mainframes, y el 445 me parecía un poco pequeño.

Conclusión

La mayoría de los circuitos adicionales que describí aquí que rodean al i8080 en la placa base de la CPU no deberían ser nuevos para los diseñadores de CPUs. De hecho, el sistema con el que trabajé posteriormente ni siquiera usaba un microprocesador de un solo chip como procesador principal; estaba compuesto por circuitos SSI (Small Scale Integration) o MSI (Medium Scale Integration) simples en una placa de circuito impreso de casi medio metro de lado. Pero el 445 fue el primer sistema del que dispuse los manuales completos y me encantó descubrir cómo conseguían sacarle tanto partido.

Hoy en día, todo está oculto en los potentes chips de CPU que diseñan y producen los principales fabricantes. Múltiples procesadores en un solo chip (multi-core le dicen), con abundante memoria integrada para caché, colas de instrucciones, predicción de saltos y muchas más funciones que ni siquiera conocemos. En aquel entonces, todo estaba a la vista de cualquiera. El propio funcionamiento interno del i8080 era comprensible. Ahora, los chips más nuevos son casi magia negra.

En cuanto a portar MP/M, también habría tenido otro gran problema. Incluso si hubiera logrado que funcionara, habría sido invendible. El software de código abierto (OSS) llegaría mucho después. Hoy en día, nadie se lo piensa dos veces antes de usar Linux, ni las innumerables aplicaciones OSS que se ejecutan en Linux, Windows o Mac. No era así en aquel entonces; los gerentes de sistemas querían software con soporte completo de sus proveedores. Mi versión de MP/M habría sido ignorada.

Ni siquiera tenía forma de intercambiar información fácilmente con Sycor para venderles (conceptualmente) mi idea. De hecho, es muy probable que algunos de sus ingenieros estuvieran considerando la misma idea, pero yo no tenía forma de saberlo. En aquella época no había correo electrónico, ni Internet, ni siquiera tablones de anuncios electrónicos. Solo había llamadas telefónicas, cartas, telex o fax, y yo estaba demasiado lejos de ellos.

Mala suerte.