Supondremos, para fines de ilustración, que estaremos trabajando con una computadora muy elemental (extremadamente primitiva, para los estándares de hoy en día) que cuenta con un conjunto extremadamente limitado de ocho instrucciones, una computadora a la cual llamaremos simplemente “computadora Alfa”. Supóngase que el conjunto de instrucciones está especificado siempre por los primeros tres “bits” (dígitos binarios) de una “palabra” binaria de la siguiente manera:
Así pues, al ejecutarse una instrucción, lo primero que la computadora hará será ver los primeros tres bits de la palabra binaria que consta de un total de nueve bits, por ejemplo:
10100101
De acuerdo al conjunto de instrucciones, los primeros tres bits de la palabra binaria 10100101 especifican la acción que deberá llevarse a cabo, la cual es un salto incondicional, ordenando que el sistema se dirija a la localidad de la memoria especificada por el domicilio 00101, obteniendo de ese domicilio la instrucción que está almacenada allí, y ejecutando la instrucción así obtenida interpretando nuevamente los primeros tres bits de la palabra binaria de nueve bits que estaba almacenada bajo el domicilio de memoria 00101.
La lectura del conjunto de instrucciones nos revela de inmediato que nuestra computadora primitiva Alfa debe contar con un sumador, puesto que la instrucción 011 especifica que se lleve a cabo una operación de adición. También debe contar con un substractor, puesto que la instrucción 100 especifica que se lleve a cabo una operación de resta. Tanto el sumador como el substractor los podemos juntar en una misma sección que llevará a cabo las operaciones elementales de procesamiento, a la cual llamaremos unidad de procesamiento central o CPU (el acrónimo de la palabra inglesa “Central Processing Unit”). Se da por hecho que se requiere también de una memoria para estar almacenando no solo instrucciones sino también datos así como resultados temporales.
A estas alturas debe ser obvio que para que la máquina haga lo que queremos que haga, debemos proporcionarle un conjunto ordenado de instrucciones en lenguaje de máquina. Tentativamente, estas instrucciones serán suministradas desde el exterior, lo cual nos permite bosquejar la computadora de la siguiente manera:
Esta ciertamente es una manera de llevar a cabo las cosas. El programa en lenguaje de máquina, dado en el lado izquierdo de la figura, puede ser almacenado en algún medio como una cinta de papel, una cinta magnética, un CD o un DVD. Pero si la memoria con la que trabaja por dentro la computadora, (la cual hoy en día es una memoria de acceso aleatorio RAM, acrónimo de la palabra inglesa “Random Access Memory”) de naturaleza puramente electrónica (usando semiconductores de estado sólido), entonces hay una forma mucho más eficiente de trabajar, y esta consiste en formularnos la siguiente pregunta: ¿Por qué no almacenar el programa en lenguaje de máquina dentro de la misma memoria? Lo que se está proponiendo, en efecto, es algo como lo siguiente:
Con el sólo hecho de hacer esto, podemos obtener una computadora mucho más rápida en sus tiempos de procesamiento. Esta idea, tan ridículamente sencilla y sin embargo de una potencia tan extraordinaria, solo la pudo haber formalizado en aquellos tiempos en los inicios de las computadoras electrónicas digitales una eminencia del calibre de John von Neumann, considerado por muchos como uno de los más grandes matemáticos del siglo XX. Aunque aún hasta nuestros días se mantiene cierta controversia sobre quién fue el autor original de este concepto, el crédito se le concede a von Neumann por haber sido él el primero en asentar tal propuesta por escrito en un trabajo titulado First Draft or a Report on the EDVAC elaborado el 30 de junio de 1945 en relación con la construcción de la computadora EDVAC.
Cualquier computadora digital que esté diseñada en este esquema es considerada como el modelo óptimo que puede permitir la velocidad de procesamiento más rápido que se pueda obtener para una computadora digital. Todas las computadoras actuales están basadas en este tipo de arquitectura, conocido como la arquitectura de von Neumann.
Estando de acuerdo en la arquitectura fundamental que habremos de utilizar de aquí en adelante, podemos imaginar que, de alguna manera, podemos almacenar en la memoria un conjunto de instrucciones como el siguiente:
Obsérvese que cada una de las instrucciones es almacenada en forma ordenada en forma binaria ascendente en las localidades de la memoria. Así pues, en la primera localidad de la memoria, la localidad 000000, estará almacenada la primera instrucción 010010100, en la segunda localidad de la memoria, la localidad 000001, estará almacenada la segunda instrucción 011010101, y así sucesivamente.
Al empezar a correr el programa, obviamente en algún punto tenemos que pedirle a la máquina que detenga su marcha para poder recoger los resultados generados. En el conjunto de instrucciones de la computadora, la instrucción 000 es la que da la orden de Alto. Si “leemos” en el lenguaje de la máquina cada instrucción, una por una, encontraremos que eventualmente llegamos a tal instrucción. Poniendo atención en la figura anterior, el lector verá que este punto en la serie de instrucciones ha sido resaltada dándole un fondo de color rosa a la línea en la cual la computadora debe detener su marcha.
Veamos ahora el siguiente programa de prueba para ejercitar un poco nuestros primeros conocimientos sobre el funcionamiento de las computadoras digitales:
Si delimitamos el punto en el cual llegamos a la instrucción de Alto, tenemos entonces un esquema como el siguiente:
Obsérvese que, más allá de la instrucción de Alto, tenemos en las cinco siguientes localidades de la memoria no instrucciones sino datos con los cuales vamos a trabajar. De este modo, dentro de cada localidad de la memoria podemos tener tanto instrucciones como datos. Esta mezcla que al principio puede parecer curiosa es el resultado directo de haber adoptado una arquitectura de von Neumann.
Obsérvese también que, en el resto de la memoria no hay información relevante que vayamos a utilizar. Naturalmente, al encender la máquina, si no cargamos algo en esas localidades de la memoria a las que no les daremos uso, de cualquier modo seguramente habrá una jerigonza de unos y ceros, sin significado alguno. Por ningún motivo se debe permitir que el programa de instrucciones conduzca hacia esa parte de la memoria que no contiene nada inteligible, porque si se permite tal cosa entonces la computadora se volverá “loca” o se “congelará”.
Después de algún tiempo, el conjunto de instrucciones de la computadora Alfa nos puede parecer sumamente limitado. Eventualmente, quisiéremos que hubiera instrucciones capaces de llevar a cabo operaciones algo más elaborada, como la siguiente operación de multiplicación:
“Tomar un número almacenado en cierta localidad de la memoria, y multiplicarlo por el número que se encuentra en el acumulador, dejando el resultado en el acumulador”
O bien, alguna operación lógica sobre dos palabras binarias, como la siguiente:
“Tomar la palabra binaria que se encuentra en cierta localidad de la memoria, y efectuar la operación lógica AND con la palabra binaria que se encuentra en el acumulador, dejando el resultado en el acumulador”
El problema es que, si se cuentan con solo tres bit de capacidad para especificar las instrucciones que pueda llevar a cabo la unidad de procesamiento central, sólo se pueden llevar a cabo ocho operaciones, puesto que esas son todas las combinaciones binarias de unos y ceros que se pueden llevar a cabo con tres bits.
PROBLEMA: Si se usan los primeros cuatro bits en la computador Alfa para especificar las instrucciones que se puedan llevar a cabo, ¿cuántas instrucciones distintas serán posibles? ¿Cuál es la desventaja que se puede anticipar al hacer tal cosa?
Si se usan los primeros cuatro bits, entonces hay 16 combinaciones posibles distintas de unos y ceros, siendo posibles por lo tanto 16 instrucciones. El problema es que, si cada palabra binaria almacenada en la memoria se mantiene a una extensión de nueve bits, el aumento en la capacidad del conjunto de instrucciones tiene que llegar a expensas de recortar un bit para la especificación de las localidades de la memoria, ya que en vez de tenerse seis bits para especificar domicilios en la memoria se tendrán únicamente cinco bits disponibles. La única manera en la cual se puede seguir domiciliando la misma cantidad de localidades en la memoria es aumentando la extensión de la palabra binaria básica de la computadora Alfa de nueve bits a diez bits.
Si en cada localidad de la memoria podemos almacenar un número binario de ocho bits, entonces el máximo número entero que podríamos almacenar (en sistema decimal) sería 127. Con una capacidad tal, si vamos a sumar dos números, la suma resultante no puede ser mayor que 127 porque ello excedería la capacidad numérica del número que podemos meter en cualquier localidad de la memoria, tendríamos lo que se conoce como un sobreflujo. Esta limitante puede parecer ridícula, porque estaríamos restringidos a llevar a cabo adiciones como 15 y 42, 26 y 61, o bien 84 y 21. ¡Y no se hable ya del manejo de números negativos, porque para representar un número negativo tendríamos que tomar un bit para representar el signo del número, lo cual reduciría a la mitad la capacidad numérica de la máquina, ya que el máximo número entero que se podría almacenar (positivo o negativo) sería 63. Estas son sumas que podemos efectuar mentalmente sin necesidad de tener que construír una máquina que con tecnología de la década de los sesentas resultaba extraordinariamente cara, y con estas consideraciones la planificación de la construcción de cualquier máquina carecería de justificación excepto como un juguete para disquisiciones de carácter meramente filosófico en instituciones académicas con amplios presupuestos. Obviamente, para poder justificar la construcción de una computadora digital, se vuelve imperativo ampliar la capacidad numérica de alguna manera. La manera más obvia de hacerlo es a través de los circuitos electrónicos requeridos para la construcción de la máquina (el hardware), aumentando la capacidad de los registros, de los sumadores, de los substractores, etc., de ocho bits a, digamos 12 bits, lo cual da una capacidad para un número como 2,048. Pero de nueva cuenta, si nos situamos en la década de los sesentas, un aumento adicional de cuatro bits en todos los registros de la máquina representaba una erogación adicional de una cantidad considerable de dinero. Tenemos que preguntarnos entonces, ¿no habrá otra manera de aumentar la capacidad numérica de una computadora digital sin vernos en la necesidad de meter sumas adicionales de dinero en hardware? Afortunadamente, sí la hay, y ello es mediante un truco por la vía de la programación, por la vía del software. El truco consiste en formar un número con dos bytes en lugar de uno. De este modo, tomamos el byte que está en la localidad 000000 de la memoria (al cual llamaremos byte alto), y le anexamos (mediante los pasos del programa) el byte que está en la localidad 000001 (al cual llamaremos byte bajo) y podemos especificar con ello un entero usando una palabra binaria de 16 bits. Esto permite ya almacenar un número máximo como 32,768. Podemos hacer lo mismo tomando el byte que está en la localidad 000010 de la memoria anexándole el byte que está en la localidad de la memoria 000011 para representar otro número con una extensión de 16 bits. De este modo, y mediante recursos exclusivos de programación, con este truco podemos llevar a cabo una ampliación hacia algo que podríamos llamar aritmética binaria de doble precisión:
Y de hecho, podemos hacer lo mismo no sólo con los números (los datos a ser manejados) sino con las mismas instrucciones, de modo tal que cada instrucción se puede especificar no con nueve bits como en el caso de nuestra computadora hipotética Alfa sino con 15 ó 16 bits, lo cual nos permite asignar de inmediato, por ejemplo, cinco bits para el conjunto de instrucciones en lenguaje de máquina que le podemos meter a la computadora (ampliando el conjunto de instrucciones hasta 32 instrucciones diferentes) quedándonos también espacio suficiente para poder utilizar memorias más grandes al tener una capacidad mayor de domiciliamiento. De este modo, además de la ampliación de la capacidad de las máquinas metiéndoles más componentes electrónicos gracias a los avances en la miniaturización de los circuitos integrados, la capacidad también puede ser ampliada con la ayuda de programación inteligente recurriendo a trucos como en éste caso. Podemos incluso recurrir a un esquema más flexible en el que cada instrucción puede ser formada por una, dos, e inclusive tres palabras binarias:
¿Y qué es lo que nos permitirá implementar este esquema tan flexible? La respuesta radica en los primeros bits de la palabra binaria que especifican la instrucción en lenguaje de máquina que será llevada a cabo. Si la instrucción en lenguaje de máquina es una instrucción como “Alto”, entonces esto no requiere de mayor información (como el especificar un domicilio de la memoria), puede ser una instrucción de una sola palabra binaria. Pero si la instrucción especifica algún domicilio largo, entonces a partir de la misma instrucción se tomará la siguiente palabra binaria o inclusive las siguientes dos palabras binarias para poder especificar la instrucción completa.
Este esquema flexible que se acaba de describir, formando palabras binarias de 16 bits anexando un byte “bajo” de ocho bits a un byte “alto” de ocho bits para aumentar la potencia de la máquina, o inclusive agregando un tercer byte para obtener una instrucción que requiere una extensión de tres bytes es precisamente lo que se implementó de inmediato en cuanto empezaron a aparecer en el mercado los primeros microprocesadores de empresas tales como Intel, Zilog y Motorola.