En este primer tema de la segunda semana vamos a ver los circuitos combinacionales. Vamos a ver qué son y vamos a ver un par de formas de diseñarlos que posteriormente iremos refinando. ¿Qué es un circuito combinacional? Es un circuito que cumple dos condiciones. La primera hace referencia a que las entradas y las salidas sólo pueden tomar los valores cero y uno, y esto es lo que significamos cuando decimos que implementan una o varias funciones de conmutación. La segunda condición hace referencia a la dependencia de la salida respecto de las entradas, y dice que el conjunto de valores que toman las salidas del circuito en un instante de tiempo t dependen única y exclusivamente del valor que toman las entradas en ese mismo instante de tiempo t. Un poco más formalmente, podemos decir que un circuito combinacional es un circuito que en cada una de sus salidas asocia un valor cero o uno a cada una de las posibles combinaciones de las entradas. Vamos a ver un primer circuito combinacional. Vamos a construir un circuito capaz de sumar dos números x e y de cuatro bits. Los números, el número mayor que podemos representar con cuatro bits es el 1, 1, 1, 1. Si sumamos los dos números mayores que podemos obtener por x e y, tenemos este resultado que como vemos necesita como máximo cinco cifras binarias para poder representar el resultado. Estos son estas cinco cifras que tenemos aquí. Lo que vamos a hacer es, a la cifra más significativa la voy a llamar acarreo de salida y además voy a permitir que me entre un acarreo de entrada que sólo puede tomar los valores 0 y 1. El hecho de definir este acarreo de entrada y este acarreo de salida, nos va a permitir más adelante utilizar este módulo para concatenar varios de ellos para construir sumadores de números mayores. Bien, sabiendo todo esto, ahora formalmente podemos definir el sumador: Dados los números x e y de cuatro cifras binarias, de cuatro bits. El sumador va a calcular la suma de x + y + el acarreo de entrada y va a poner el resultado en los cuatro bits z3 z2, z1y z0, más el acarreo de salida. Esto sería la descripción funcional, donde lo único que hemos de tener en cuenta es que todas la operaciones se realizan en base dos. La ventaja de trabajar con circuitos en los que las entradas y salidas sólo pueden tomar valores discretos, es que podemos describir el funcionamiento de un circuito exhaustivamente a través de una tabla, en la que pongamos todas las entradas x3, x2, x1, x0; y3, y2, y1, y0, y el acarreo de entrada que me permitiréis escribirlo como a_in simplemente para ser más rápidos ¿vale? Y que en la parte derecha de la tabla vamos a poner las salidas, el acarreo de salida que lo voy a llamar a_out, y los cuatro valores z3, z2, z1, z0. Y aquí iremos poniendo ..., si todas las entradas son 0, según la descripción funcional que tenemos aquí, las salidas han de ser 0. Si las entradas son 0 y el acarreo de entrada es 1, la salida será 1. Así sucesivamente. Por ejemplo, si la entrada es 7 y 6 en decimal, y el acarreo de entrada es 0, la suma es 13, que quiere decir acarreo de salida igual a 0. Y 1, 1, 0, 1, que es el 13 en binario. Y finalmente en el último caso si sumamos 1, 1, 1, 1, con 1, 1, 1, 1, y con acarreo de entrada igual a 1, pues obtendremos un resutlado de todo 1s. Esta es la misma tabla, mejor escrita. Vamos a utilizar una memoria ROM para implementar esta tabla. Un breve recordatorio de lo que es la memoria ROM que ya vimos en la semana anterior, ¿vale? La memoria ROM es un dispositivo capaz de guardar en filas una serie de informaciones. Cada una de estas filas recibe el nombre de palabra, y se le asocia una dirección. Esta sería la palabra de dirección 0, dirección 1, etcétera, hasta dirección m. De manera que en las entradas se pone la dirección y en las salidas se obtiene el contenido de la palabra cuya dirección se ha puesto en las entradas, es decir, por ejemplo si yo pongo aquí 0, 0, 0, 1, la palabra de dirección 1; el contenido de la palabra de dirección 1 es el que se volcará a la salida, ¿vale? Bueno, de acuerdo. Pues vamos a utilizar ahora una memoria ROM para implementar esta tabla. La memoria ROM, lo que voy a hacer es utilizar las combinaciones de entrada como direcciones, es decir, las entradas a la memoria ROM van a ser todas las entradas al circuito x3, x2, ... , x0, y3, ... bien, todas las entradas, y0 y el acarreo de entrada. Y entonces voy a poner, por ejemplo, en la dirección 9 que sería esta, en la palabra de dirección 9 voy a poner precisamente el valor de las salidas del circuito cuando las entradas son 9 en binario. Voy a poner el 0, 0, 1, 0, 1, ¿de acuerdo? Es esto lo que colocamos aquí. Y esto lo vamos a hacer pues, con todas las palabras de la memoria, es decir, en la palabra de dirección 0, yo voy a poner el contenido de las salidas cuando las entradas son 0. En este caso son todos 0, etcétera. Y las salidas serán el a_out z3, z2, z1, z0 z La idea es que toda la parte de la tabla correspondiente a las salidas tal cual se vuelca en la memoria ROM y se utilizan las entradas para direccionar la salida correspondiente en cada momento. Aquí tenemos la memoria ROM que implementa esta tabla. Fijaros por ejemplo, si yo cojo la dirección 2 y pongo estos valores por las entradas, lo que va a hacer esta memoria ROM es devolverme la palabra de dirección 2, que es esta, y que coincide exactamente con la salida cuando las entradas son las que yo le acabo de poner. Fijémonos que puesto que tengo nueve entradas y aquí yo he escrito todas las posibles combinaciones de 0s y 1s, esta tabla tiene un total de 2 elevado a 9, de 512 filas, y por lo tanto necesito una memoria que contenga 512 palabras. Como además cada una de las palabras han de guardar la información sobre todos los bits de salida, necesito que cada una de estas palabras tenga cinco bits. Bueno, fijaros que esto es muy potente, porque lo que estoy diciendo es que yo puedo construir cualquier circuito combinacional de n entradas y m salidas, utilizando una memoria ROM de 2 elevado a n palabras, cada palabra conteniendo m bits. Y con esto puedo construir, insisto, cualquier circuito combinacional. El problema de esta aproximación, el inconveniente, es que habitualmente es ineficiente, en el sentido de que puedo construir el mismo circuito utilizando menos recursos si en vez de utilizar una memoria ROM utilizo puertas lógicas. Y esto es lo que vamos a ver a continuación. Primero os dejo con una pequeña pregunta para que contestéis. Bien, pues ahora vamos a ver como construir circuitos combinacionales utilizando puertas lógicas. Veremos una primera aproximación que más adelante refinaremos. Estamos con el mismo circuito. Antes de empezar a diseñarlo, lo que nos podemos dar cuenta es que este circuito yo lo puedo construir concatenando cuatro circuitos más pequeños como este, capaces de sumar dos números x_i y y_i de un bit con un acarreo. Esto es totalmente generalizable, de manera que si yo en vez de en vez de construir un sumador de números de cuatro bits quiero construir un sumador de números de seis bits, tendría que poner aquí dos módulos más. Es un diseño modular totalmente generalizable. Nos vamos a centrar en este pequeño circuito de aquí. Dados x_i, e y_i, y el acarreo de entrada, si yo sumo por ejemplo 1 más 1 con un acarreo 0, la suma en base dos me da 1, 0. Este me aparece por z_i, y este 1 lo tomo como un acarreo de salida que me va a parar al siguiente módulo. Si yo sumo 1, 1, 1, la suma en binario es 1, 1. Este 1 me aparecería por z_i, y este me aparecería por el acarreo de salida. Vamos a ver como diseñar exactamente este módulo de aquí. Otra vez. ... vamos a proceder igual. Como es un circuito en que las entradas y salidas están limitadas a 0,1, puedo representar su comportamiento por una tabla donde, como entradas, tendré el x_i, la y_i y el acarreo de entrada. En este caso, y a partir de ahora, lo vamos a llamar c_i. La c viene de la palabra anglosajona "carry", que es lo mismo que acarreo, ¿vale?. Y como salidas tendremos el acarreo de salida, que lo llamaremos c_o y z_i. Y aquí tenemos el conjunto de todas las combinaciones, esto es un poco más sencillo. Si las entradas son 0, 0, 0, la suma es 0. Si las entradas son 0, 0, y el acarreo de entrada es 1, la suma es 1. Si sumamos el 0 con el 1 con acarreo de entrada 0 nos vuelve a dar 0, 1, etc. Así podríamos ir repasando todos los valores. 1, 0, 1, 1. teniendo en cuenta que en el fondo yo estoy obteniendo los valores de salida siguiendo el algoritmo de la descripción funcional del circuito. Esta es la tabla ¿vale? Vamos a ver como lo podemos construir con puertas lógicas. Os recuerdo que definimos la semana pasada tres tipos de puertas, la AND, la OR y el inversor. Decíamos que la puerta AND genera un 1 en la salida si y sólo si sus dos entradas son 1, de hecho, genera un 1 si la x y la y, de ahí el nombre de AND, toman el valor 1. La puerta OR genera un 1 a la salida si por la x o por la y, de ahí el nombre de OR, le está entrando un 1, es decir, el funcionamiento es este. Y el inversor cambia 0s por 1s y 1s por 0s. Si entra un 0 aparece un 1 a la salida y vice versa. Aquí tengo la misma tabla de verdad. Vamos a ver, primero vamos a construir la salida, construir un circuito que me genere la salida c_o. Lo primero que necesito darme cuenta es de que yo necesito que c_o tome el valor 1 cuando por sus entradas x_i, y_i, y c_i me aparece, o 0, 1, 1, o 1, 0, 1, o 1, 1, 0, o 1, 1, 1. Puedo utilizar una puerta AND a la que le entre el x_i convenientemente pasado por un inversor; la y_i y la c_i. La salida de esta puerta toma el valor uno si y sólo si la combinación de entrada es 0, 1, 1. Para cualquier otra combinación de entrada, el valor de salida de esta puerta será 0. Puedo hacer lo mismo con la combinación 1, 0, 1. No pongo ya las entradas, están siempre en el mismo orden. Puedo hacer lo mismo para la combinación 1, 1, 0. Y puedo hacer lo mismo para la combinación 1, 1, 1. Bien. Yo necesito que c_o tome el valor 1 si aparece como entrada esta combinación o esta, o esta, o esta. Es decir, si a la salida de esta puerta AND tengo 1 o a la salida de la segunda puerta AND tengo un 1 o a la salida de la tercera puerta AND tengo un 1 o a la salida de la cuarta puerta tengo 1. Y esto lo puedo hacer precisamente con una puerta OR. Si yo entro las salidas de las puertas AND a una puerta OR aquí tengo una salida que va a tomar el valor 1 en las mismas condiciones que tiene que tomar el valor uno la salida c_o Aquí voy a tener un 1 si y sólo si las combinaciones de entrada son una de estas cuatro. Pues bien, ya tengo el circuito diseñado para la salida c_o. Podríamos hacer lo mismo para la salida z_i. En todo caso aquí hay un detalle que vale la pena que nos demos cuenta, y es que de la misma manera que yo he puesto estas puertas para, de alguna manera, representar o implementar la salida 1 en estos dos casos, yo me puedo dar cuenta de que realmente, con que mire que la x_i y la y_i toman el valor 1, la salida ha de ser 1 independientemente de cual sea el valor de c_i. Es decir, yo podría borrar estas dos puertas y decir que para mi es suficiente que la x_i y la y_i tomen el valor 1. Así bien pues podría sustituir las dos puertas AND por una única puerta AND donde entrara el x_i y el y_i. Este es un circuito que también funciona, que también me implementa la salida c_o tal como yo deseo, pero que es más sencillo; tiene al menos un inversor menos y una puerta AND menos, por lo tanto va a ser un circuito más barato, que va a consumir menos, etc, etc, etc. Lo que necesitamos es una herramienta que nos permita implementar cualquier circuito digital utilizando el menor número posible de puertas, pero sin tener que caer en la cuenta de que aquellas dos puertas, podíamos sustituirlas por una, es decir, necesitamos una herramienta que nos permita hacer esto de una manera más automatizada. Y esta herramienta es el Álgebra de Boole y es a lo que vamos a dedicar el segundo tema de esta semana. Antes de pasar a este segundo tema, os dejo con un pequeño ejercicio. Se trataría de que intentéis implementar con puertas lógicas las salidas z_i del sumador. Intentadlo y, más adelante, os doy la solución. Vamos a ver como podemos implementar la salida z_i. Vamos a hacer exactamente lo mismo que hemos hecho con c_o, es decir, identificamos las combinaciones de entrada que me generas 1s en las salidas z_i, son las combinaciones 0, 0, 1; 0, 1, 0; 1, 0, 0; y 1, 1, 1. Y para cada una de ellas hacemos un pequeño subcircuito con una puerta AND y las entradas x_i, y_i, y c_i que van pasando o no por inversores, dependiendo de que la combinación de entrada sea para cada una de estas entradas, 0 o 1. La siguiente sería x_i barra, me dejáis x_i invertida, me dejáis que ponga las entradas para dibujarlo un poco más rapidamente. Están siempre en este mismo orden. La tercera combinación sería esta. Y la cuarta sería esta. Y finalmente lo que hemos de hacer es poner una puerta AND que, perdón una puerta OR que sume todas estas salidas. Aquí no hay ningún truco, digamos, como el que hemos visto anteriormente con estos dos 1s de c_o. No podemos reducir nada. Una consideración, fijaros que aquí hay uno, dos, tres, cuatro, cinco, seis inversores que de hecho no son necesarios. Lo he dibujado así para no complicar demasiado en conexionado, pero yo podría haber dibujado x_i, y_i, c_i Vamos a tirar tres líneas y después cada una de ellas invertidas. Y entonces decir, a ver esta primera puerta la primera puerta AND me ha de recojer x_i invertida, que es esta señal de aquí. Me ha de recojer y_i invertida y me ha de recojer c_i, que la tengo aquí. La puerta dos, esta segunda me ha de recojer x_i invertida que la tengo aquí. La y_i y la c_i invertida que la tengo aquí, etc. Con lo cual en total necesitaríamos cuatro puertas AND, una puerta OR y solo tres inversores. Bien, pues ya tenemos el ejercicio resuelto. Aquí tenéis el circuito completo y bien dibujado. Como véis en total necesitaríamos siete puertas AND, dos puertas OR y tres inversores. Más adelante cuando veamos otro tipo de puertas que es la XOR, veremos que este circuito se puede hacer todavía más simplificado. Con esto acabamos el primer tema de esta semana. Un breve resumen, hemos visto lo que son los circuitos combinacionales. Hemos diseñado circuitos combinacionales utilizando memorias ROM y hemos hecho un primer intento de diseño de circuitos combinacionales utilizando puertas lógicas.