Regístrate | Conectar
El Tamiz Libros Recursos Series Únete 22 Users Online
Skip to content

Computador mágico XVIII – Registros y memoria




La serie “El computador mágico” está disponible también en forma de libro.

En el último artículo de la serie presentamos un primer ordenador rudimentario:

  • Un estado almacenado en unos cuantos biestables.
  • Un circuito combinacional de realimentación que calculaba el estado siguiente a partir del estado actual y las entradas.
  • Una señal de reloj que pasaba el cálculo de “estado siguiente” al interior de los biestables de forma síncrona.
  • Y una etapa de salida combinacional que calculaba lo que mostrar al usuario en función del estado de los biestables.

Pero, si os acordáis, nos molestaba bastante tener que cambiar un montón de cosas cada vez que teníamos que cambiar el algoritmo que estaba ejecutando el computador, así que nos inventábamos la arquitectura de von Neumann para separar el hardware de la máquina del algoritmo que ejecutaba, al que llamamos software.[1]

A eso es a lo que dedicaremos los próximos capítulos, pero para poder llegar a ella de forma fácil tenemos que dedicar un par de artículos a algunos elementos accesorios. Así, cuando lleguemos a explicar la arquitectura de von Neumann en sí misma, no necesitaremos detenernos en estos elementos, porque los tendremos dominados.

 

 

El primero de estos elementos es el registro. Es muy sencillo: un registro no es más que unos cuantos biestables puestos uno al lado de otro, de modo que en lugar de almacenar un bit, almacenen una palabra completa. Por ejemplo, lo siguiente es un registro de 8 bits:

Fíjate en cómo el registro son simplemente 8 biestables puestos uno junto a otro. Hay una entrada de Clear (limpiar) que simplemente va a los biestables para ponerlos a 0 (por ejemplo, durante la inicialización del sistema). Lo único curioso en este registro es la entrada de Ena (enable, habilitar), que funciona de la siguiente manera: si es un 1, al negarlo y hacer el OR con la señal de reloj simplemente hace que la señal del reloj vaya a los biestables; si es un 0, lo que llega a las C de los biestables es siempre un 1, y por lo tanto la entrada no se almacena dentro (recuerda que es el flanco de subida el que introduce la entrada al biestable, no el hecho de que C sea 1 o no, y en este momento el flanco no llega nunca al biestable). Es decir, habilita la entrada al registro.

Lógicamente, los registros no tienen por qué tener 8 bits. Supongo que, dado que hace unos cuantos capítulos presentamos una ALU de 16 bits, no te sorprende que vayamos a usar registros de 16 bits. También usaremos de otras medidas. A veces se indica mediante el ancho de la entrada, como por ejemplo en los símbolos que ponemos a la derecha. A veces se dibujan en horizontal y otras en vertical, y además hay variaciones sobre el símbolo, pero todos se deducen siempre del contexto.

En ocasiones los registros tienen alguna entrada o salida más, como por ejemplo un bit de salida que indica si el contenido es todo 0, o cosas así, pero imagino que a estas alturas ya sabes que eso es simplemente añadir algo de lógica combinacional alrededor del registro.

Así que, si juntamos varios biestables, tenemos un registro. ¿Y si juntamos varios registros? Entonces lo que tenemos es una memoria. La forma más habitual de memoria, que será la que necesitemos en adelante (y por eso será la que contaremos) es lo que se llama memoria de acceso aleatorio (en inglés RAM, Random Access Memory). Vamos a ir viéndolo con cuidado, porque tendremos que juntar los registros con cierta astucia.

Podemos partir de un esquema como el siguiente:

Lo primero es darte cuenta de los bloques que lo componen. El trapecio de la izquierda es un decodificador, que ya hemos visto antes. Por si no lo recuerdas, tiene n entradas y 2^n salidas, y lo que hace es poner un 1 en una sola de las salidas dependiendo del valor binario de la entrada (y un 0 en todas las demás). El trapecio de la derecha es un multiplexor, que también hemos visto. De nuevo por si no lo recuerdas, tiene un montón de entradas (en nuestro caso 2^12=4096) y una señal de control, de 12 bits en este caso, que manda hacia la salida solo una de las entradas. Cada una de las entradas (y consecuentemente también la salida) tienen 16 bits. Las cajas rectangulares son registros como los que hemos visto unos cuantos párrafos más arriba, con la entrada por arriba y la salida por abajo (cuidado, no te confundas, que en los símbolos de antes los pusimos al revés). Fíjate en que no hemos puesto la señal de reloj… eso no quiere decir que no la tengan: simplemente está implícita. Aunque no la dibujemos, para ahorrar rayas por medio del diseño, no olvides que la señal de reloj sigue estando ahí y que esos rectángulos, en el fondo, no son más que 16 biestables uno junto a otro.

El diseño tiene 3 interfaces reconocibles. R/W’ es la señal de selección de lectura-escritura. A es la entrada del bus de direcciones, y tiene 12 bits. D es el bus de datos, de 16 bits, y es a la vez entrada y salida (dependiendo del valor de R/W’). Vamos a verlos uno por uno.

La señal R/W’ indica si se debe leer una posición de memoria o escribir en ella. Esa notación, R/W’, es muy utilizada: indica que si es un 1 es R (read, leer) y si es un 0 es W (write, escribir). Fíjate en la comita que acompaña a la W, para indicar que está negada, y por lo tanto está activa cuando sea un 0. ¿Está claro?

El bus A es el bus de direcciones (por el inglés address). Indica la dirección de memoria a la que se está accediendo (sea lectura o sea escritura). Es muy-muy habitual indicar las direcciones de memoria en hexadecimal. Así, en vez de hablar de las direcciones 1, 2, 3, 10, 11, 12, 100, 101, 102…, hablaremos respectivamente de 0×001, 0×002, 0×003, 0x00a, 0x00b, 0x00c, 0×064, 0×065, 0×066… Dado que el bus tiene 12 bits esa dirección va desde 0 hasta 2^12, es decir desde 0×000 hasta 0xfff.

El bus D es el de datos (por el inglés data). Dependiendo de si el bit R/W’ es un 0 o un 1 actúa como entrada o como salida, respectivamente. Si R/W’ es un 1, quiere decir que estamos leyendo. Por lo tanto, el circuito pondrá en la salida D el valor de la posición de memoria que indique A. En cambio, si R/W’ es un 0, quiere decir que estamos escribiendo. Por lo tanto, el circuito introducirá en la posición de memoria que indique A el valor de la entrada D. ¿Entendido? Fíjate en cómo D actúa a veces como entrada y a veces como salida, y por eso la salida del multiplexor de la derecha está conectada también hacia D. Esto es lo que se llama un bus. Veremos los buses con un poco más de detalle más adelante.

Supongo que el funcionamiento no es difícil de entender, ¿no? El decodificador pone un 1 solo en la línea que está seleccionada por el bus A. Esa línea se lleva hacia el enable de ese registro… pero antes pasa por una puerta AND junto con el bit R/W’. Al juntar ambas cosas, si el bit R/W’ es un 0 (write, escribir), el enable que selecciona el decodificador llega hasta el registro correspondiente, y se introduce en él lo que llegue por D. En cambio, si R/W’ es un 1 (read, leer), da igual la línea que seleccione el decodificador, porque debido al AND, a los enable de los registros siempre llegará un 0; y por lo tanto, no se escribirá nada en ninguno de los registros. En este caso lo importante es el multiplexor de la derecha, que toma el valor del registro seleccionado por A y lo lleva hacia D.

Como nota profunda, has de saber que esto, en realidad, no funciona. Entre la salida del multiplexor y la salida del circuito es necesario poner unas puertas triestado, controladas por R/W’, para evitar que cuando conectemos otros dispositivos al bus (incluido el que nos está enviando D) entren en conflicto y tengamos problemas. Pero eso no es necesario para entender el resto de la explicación, y dejaremos la explicación profunda para dentro de tres capítulos. De momento, quédate con el concepto y olvídate del funcionamiento de la puerta triestado (si no puedes resistir el gusanillo, piensa en esa puerta triestado simplemente como un interruptor controlable por R/W’).

Fíjate en que hemos usado un tamaño de palabra de 16 bits, que es lo mismo que era capaz de gestionar nuestra ALU. Hemos usado un tamaño de dirección de 12 bits, por lo que podemos direccionar palabras desde la 0×000 a la 0xfff (es decir, en decimal, desde 0 hasta 4095). Vamos a representar simbólicamente nuestra memoria de este modo (nuestro dibujo tiene solo 2048 palabras, más abajo verás por qué):

Resumamos la palabrería: una memoria almacena palabras. Si se le pide que lea, pone en el bus D el contenido de la posición que le indique el bus A. Si se le pide que escriba, almacena el contenido del bus D en la posición que indique el bus A.

¿Es esto el diseño de una memoria real? Pues no mucho, por diversos motivos. Esto es un diseño muy académico, muy didáctico, muy simbólico… pero poco práctico.

Por un lado, las memorias de verdad, aunque tengan un tamaño de palabra de 16 bits (hoy en día lo habitual es 32 bits o incluso 64 bits) permiten direccionar por separado cada byte. En nuestro diseño hemos preferido obviar eso para simplificar la explicación, pero, por ejemplo, si tuviéramos que aplicarlo a los procesadores Intel de 32 bits habituales en la informática doméstica (por ejemplo el Intel Pentium y sucesores), diríamos que el bus A tiene 32 bits y que por lo tanto puede direccionar 2^32 bytes distintos. Supongo que a estas alturas del siglo conoces la terminología kilo, mega, giga y tera para referirse a los bytes. Un kilobyte son 1024 bytes (es decir, 2^10 bytes). Un megabyte son 1024 kilobytes (2^10 kilobytes = 2^20 bytes). Un gigabyte son 1024 megabytes (2^10 megabytes = 2^30 bytes). Y un terabyte son 1024 gigabytes (2^10 gigabytes = 2^40 bytes). Es casi como los múltiplos del sistema internacional.[2] Entonces, nuestro Intel con bus A de 32 bits puede direccionar 2^32=4 GBytes de memoria. Además, el tamaño de palabra de ese procesador es de 32 bits, que es lo que mide el bus D. Por lo tanto, en un procesador como ese puedes referirte por ejemplo a la posición de memoria 0x0a0b0c04 con tamaño 32, pero también puedes referirte por separado a la posición 0x0a0b0c05, la 0x0a0b0c06 o la 0x0a0b0c07. Que esto sea más o menos fácil o eficiente depende de muchas cosas. En nuestro ejemplo hemos obviado esto, porque simplifica mucho la explicación sin desvirtuar el concepto. Así que, a pesar de esta nota, cuando utilicemos la memoria de aquí en adelante (al menos hasta que digamos algo en contra), supondremos que la dirección se refiere a una palabra completa.

Por otro lado, es muy habitual que, aunque el bus A tenga 12 bits, la memoria no tenga 2^12 entradas, sino muchas menos. Por ejemplo, si estás leyendo esto en un ordenador doméstico es muy probable que su bus A mida 32 bits (es decir, que podría tener una memoria de 2^32=4 GB), pero hasta no hace mucho tiempo era habitual tener mucha menos memoria que eso: 1 GB, por ejemplo. Incluso nosotros, en futuros capítulos, usaremos una memoria de solo 2048 palabras, en lugar de las 4096 posibles. Ya veremos por qué. Eso no supone mucho cambio: simplemente, en lugar de haber 4096 registros, habrá solo 2048 registros… pero el bus A seguirá siendo de 12 bits. Por lo tanto, si el bus A contiene una dirección que la memoria no posee (porque cae fuera de esos 2048 registros)… pues nada, lo ignora y listo.

Finalmente, el diseño que hemos explicado no se usa mucho. Es un diseño sencillo y eficiente… pero ocupa muchísimo espacio en el chip. Probablemente a estas alturas de la serie ya te habrás olvidado de las puertas y transistores, pero echa memoria atrás: con los transistores formábamos puertas; con ellos, biestables y lógica combinacional; con ellos, registros. Al final, para cada posición de memoria, entre el bit almacenado en sí y todas las cosas que le ponemos alrededor, necesitamos un montón enorme de transistores. Es posible obtener el mismo almacenamiento de forma mucho más eficiente si diseñamos directamente los transistores, en lugar de pasar por todos esos bloques de abstracción. No obstante, esto queda muy de lejos más allá del alcance de esta serie, y además no aporta nada conceptualmente importante, así que también lo vamos a obviar.

Un pequeño resumen antes de terminar: hemos unido varios biestables para formar un registro. Y luego hemos unido astutamente un montón de registros para formar una memoria. En el próximo capítulo veremos cómo hacer una rudimentaria pantalla que también nos hará falta más adelante.

 

  1. Obviamente no nos lo inventábamos nosotros, se lo inventó von Neumann. []
  2. Si quieres ser estricto, los prefijos correctos son kiki, mebi, gigi y tebi, pero nadie los usa. []

Sobre el autor:

J ( )

 

{ 3 } Comentarios

  1. Gravatar amigo | 27/06/2013 at 11:44 | Permalink

    “lo que llega a las C de los biestables es siempre un 1″

    ¿Qué es C?

    Saludos

  2. Gravatar Brigo | 27/06/2013 at 07:06 | Permalink

    Mira por dónde me voy a enterar lo que es un bus de datos aquí. :-)

  3. Gravatar Brigo | 27/06/2013 at 07:08 | Permalink

    @ amigo , C es una de las entradas de los biestables según se explicó hace algunos capítulos. Mejor te lees el capítulo y te enteras con una explicación bastante mejor que la que te pueda dar yo.

Escribe un comentario

Tu dirección de correo no es mostrada. Los campos requeridos están marcados *

Al escribir un comentario aquí nos otorgas el permiso irrevocable de reproducir tus palabras y tu nombre/sitio web como atribución.