En esta lección vamos a introducir un nuevo conjunto de componentes que nos resultarán muy útiles en el diseño de circuitos digitales.El primero de ellos es el multiplexor. Aquí tenéis el multiplexor más sencillo que se puede definir, es el llamado multiplexor dos a uno, que tiene dos entradas de datos x0 y x1, una señal de control y una salida de datos. Lo que hace este dispositivo es muy sencillo. Dependiendo de si la señal de control está a 0 o a 1, conecta la salida con la entrada x0 o con la entrada x1. Si la señal de control es 0, x0 queda conectada a la salida y si la señal de control es 1, es x1 la que queda conectada a la salida. Un poco más formalmente, este dispositivo realiza la siguiente función booleana, "y" igual a control negado por x0 más control por x1. Fijaros que cuando control es 0 esto vale 1, esta parte desaparece y por lo tanto "y" queda a igual a x0, y cuando control es igual a 1, es esta parte la que desaparece la señal de control esta a 1 con lo cual "y" queda igual a x1. la señal de control esta a 1 con lo cual "y" queda igual a x1. En el fondo, la función básica de un multiplexor es definir dinámicamente los caminos o las conexiones por los cuales van a discurrir los datos. Aquí podemos ver un ejemplo: La entrada del circuito C va a ver la salida del circuito A o la salida del circuito B dependiendo del valor de esta señal de control. Si control vale 1 el circuito C estará viendo por su entrada la salida del circuito A, y si la señal de control es 0 estará viendo la salida del circuito B. Entonces, si os imagináis un circuito complejo, con muchos multiplexores modificando el valor de las señales de control de cada uno de estos multiplexores, yo puedo definir los caminos de datos por los cuales va a discurrir la información. Aquí tenemos otro multiplexor un poco más complejo, el multiplexor 2 a 1 de m bits. El funcionamiento es exactamente igual al multiplexor que hemos visto ahora, salvo que las entradas y las salidas son buses de m bits en vez de líneas simples de un bit. Por ejemplo, si m es igual a 4, lo que tendremos es un multiplexor, desarrollando los buses estos de entrada, con un total de 8 entradas y 4 salidas. Estas entradas corresponderán a x0, vamos a llamarla x03 a x00, estas corresponderán al x1, vamos a llamarlas x13, x12 ... x10, y estas serán las salidas y3 a y0. Cuando la señal de control es igual a 0 lo que hace es, fijaros que el 0 está aquí, es conectar x03 a la salida y3, x02 a la salida y2, etc., x00 a la salida y0. Y si control es igual a 1 pues hará exactamente lo mismo pero con las entradas x13 a x10. uno cero. Igual que hemos definido multiplexores dos a uno, también podemos definir multiplexores 4 a 1, que tienen 4 entradas de datos. Fijaros que para poder seleccionar qué entrada de datos queda conectada en cada momento a la salida, necesitamos 2 señales de control. El funcionamiento es trivial: si c1 c0 toma el valor 00, es x0 el que se conecta a la salida "y". Si c1 c0 toma el valor 10, será x2 el que se conecte a la salida "y", etc. Esto sería un multiplexor de 4 a 1. Fijaros que este 4 es 2 elevado al cuadrado, y por lo tanto necesitamos 2 señales de control, en el multiplexor 2 a 1 necesitabamos 1. Y lo mismo que hemos hecho ahora, podríamos generalizarlo y construir multiplexores 8 a 1; como el 8 es 2 elevado a la 3 necesitaríamos 3 señales de control. O multiplexores 16 a 1 que tendrían 16 entradas de datos y 4 señales de control, etc., o en general multiplexores de 2 elevado a n a 1, que necesitarían n señales de control. De hecho, hay otra manera de nombrar los multiplexores, no en función del número de entradas, sino en función del número de señales de control. Así un multiplexor 2 a 1 recibe también el nombre de multiplexor de orden 1, porque requiere una única señal de control. Un multiplexor 4 a 1 recibe también el nombre de un multiplexor de orden 2; este sería un multiplexor de orden 3 ... En general, el mutiplexor 2 elevado a n a 1 sería, o recibiría también el nombre alternativo de multiplexor de orden n. Os dejo ahora con un pequeño ejercicio, tratad de hacerlo y en todo caso, vemos la solución un poco más adelante. Bueno pues aquí tenéis una posible solución. Hemos construido dos árboles de multiplexores, uno para generar la salida y0 y otro para generar la salida y1. Las señales de control c0 que llegan a los dos árboles y c1 son exactamente, las mismas, es decir, estas dos señales son las mismas, y por supuesto estas dos señales también son las mismas, c0 y c1; y ahora vamos a ver algún caso. Por ejemplo imaginemos que C1 C0 toman el valor 00. ¿Qué quiere decir? Si c1 es igual a 0, quiere decir que y0 habrá quedado conectada a esta señal, a esta entrada, y puesto que c0 también es 0, la salida de este multiplexor habrá quedado conectado a x00, es decir, habremos establecido este camino y por lo tanto y0 habrá quedado conectada a x00. En el segundo árbol de multiplexores el funcionamiento será el mismo. En el segundo árbol de multiplexores el funcionamiento será el mismo. Habremos establecido este camino y, por lo tanto, y1 habrá quedado a conectado a x01, que es exactamente lo que queríamos, que cuando c1 c0 tomen el valor 00 las y's, las salidas, queden conectadas x00 y x01. Podemos ver un segundo caso, ¿qué pasa si c1 c0 toman por ejemplo el valor 10? 10 quiere decir que, la c0 vale 0 y la c1 vale 1. Aquí Quiere decir que y1 quedará conectado exactamente a x21, vamos a ponerlo aquí. Y que y0 habrá quedado conectado exactamente a x20 que, otra vez, es exactamente lo que queríamos, que cuando las entradas fuesen 10, las salidas quedasen conectadas a x21 y a x20. Además de su utilidad como elementos capaces de definir dinámicamente los flujos de datos, los multiplexores se pueden utilizar también para implementar funciones booleanas definidas por tablas. Veamos un ejemplo. Supongamos que queremos construir o implementar las funciones y1 y y0 dadas por esta tabla. Son funciones que como véis dependen de tres variables, x2, x1, x0. Lo que haríamos es construir dos árboles de multiplexores, uno por cada una de las funciones que queremos implementar, donde las variables de entrada de las dos funciones las vamos a colocar como señales de control exactamente en este orden. Fijaros que la señal más significativa de la tabla de verdad, x2, aparece asociada al multiplexor más cercano a la salida, en cada uno de los casos. Y lo que vamos a hacer es simplemente coger el valor que toma la función y1 en este caso, tal como está en la tabla de verdad, colocarlo a las entradas de este primer árbol de multiplexores y, evidentemente, hacer lo mismo con yo: coger esta columna y colocarla como entrada al segundo árbol de multiplexores. De manera que, suponed que las variables de entrada valen todas 0, aquí tendríamos 0s, quiere decir que tendríamos establecido este camino, y por lo tanto y1 tomaría el valor 0, que es este 0 de aquí. En el segundo árbol ocurriría lo mismo, tendríamos definido este camino y el y0 sería igual a 0, este 0 es exactamente este de aquí. Veamos otro ejemplo. Cuando las x valen 1 0 1, aquí tendría un 1 0 1, 1 0 1, y esto me definirá el camino pues, 1 0 1. Este 1 de aquí es exactamente este 1 de la tabla de verdad. Cuando x0, x1, x2 valen 1 0 1, este árbol de multiplexores me saca un 0 por y1, exactamente lo mismo que me está diciendo la tabla de verdad. En el segundo árbol de multiplexores, pues lo mismo, ¿no? Si aquí tenemos un 1, hemos definido éste camino de datos, éste 0 de aquí, se corresponde, exactamente, con este 0 de la tabla de verdad. La implementación de funciones booleanas con multiplexores se basa en la aplicación interactiva de una regla de descomposición conocida con el nombre de "Ley de Shannon", que dice que cualquier función booleana se puede expresar como la suma de dos productos: El primer producto, formado por cualquier variable, aquí hemos escogido x0 pero podríamos haber puesto cualquier variable. x0, la variable negada, multiplicada por el valor de la función, donde la variable x0, la hemos substituido por un 0, y el segundo producto, formado por ésta misma variable, ésta vez sin negar, por el valor que toma la función, cuando en la variable x0 hemos colocado un 1. Bién, ésta regla de descomposición puede implementarse directamente con un multiplexor, utilizando la variable como señal de control. Cuando x0 es 0, establecemos esta conección, y la f me queda conectada a f(0,x1,...) y el resto de las variables, lo mismo que me está diciendo la regla de descomposición, que cuando la x0 es 0 éste término desaparece, éste término es 1, y por lo tanto la función, coincide con f(1, x1,...) y el resto de variables. Esta misma regla de descomposición podríamos ir aplicándola iterativamente. Por ejemplo, podríamos aplicarla a la función f(0, x1, x2,...) y decir que es igual a x1-barra, por f(0, 0, x2,...) más x1 por f(0, 1, x2, ...). más x1 por f(0, 1, x2, ...). Y a su vez, esto lo podríamos implementar, por un multiplexor, controlado por x1 uno, donde aquí tendríamos, f(0, 0, x2, ...), y aquí donde aquí tendríamos, f(0, 0, x2, ...), y aquí tendríamos f(0, 1, x2, ...), etc. Y esto podríamos tendríamos f(0, 1, x2, ...), etc. Y esto podríamos hacerlo, tantas veces como quisiéramos. Fijaros que en cada paso, al pasar de aquí a aquí hemos, digamos, extraído una variable. Ésto es una función de n variables, éstas son funciones de n-1 variables, éstas son funciones de n-2 variables porque el resto de variables originales son valores fijos. De manera que, repitiendo esta iteración n veces, acabaríamos llegando a unos multiplexores, donde las entradas, siempre serían de la forma f(0, 0, 1 ,1, ...), es decir, no tendrían ya ninguna variable. Y ésto es precisamente el valor de la función cuando las entradas son 0 0 1 1 ... cero, uno, uno. Es decir, ésto es una entrada en la tabla de verdad, de la función f. Vamos a ver ahora, una nueva técnica de diseño que combina multiplexores con bloques de memoria ROM. Ésta es una técnica que se utiliza en las FPGAs y otros dispositivos programables que veremos más adelante. De hecho, lo veremos en las últimas semanas del curso, pero que os voy a dar un pequeño adelanto. Las FPGAs son dispositivos que, cuando se explican a un nivel muy general, se dice que contienen millares de puertas lógicas más un interconeccionado que el usuario puede configurar a voluntad para construir los circuitos que desea. Pero en realidad, no son puertas lógicas, lo que contiene, o al menos, no solo puertas lógicas, sino un conjunto de bloques, cada uno de los cuales puede implementar un cierto número de funciones booleanas. Pues bién, estos bloques se componen de pequeñas memorias ROM y de multiplexores A estas pequeñas memorias ROM se les llama "Look Up Tables", o LUTs en general A estas pequeñas memorias ROM se les llama "Look Up Tables", o LUTs en general y son memorias, en general, de 2 elevado a n palabras, de 1 bit. Donde n, además, es un número relativamente pequeño. Veamos un ejemplo de cómo se pueden combinar éstas LUTs con los multiplexores: Supongamos que tenemos unas memorias ROM,unas LUTs, de 2 elevado a 6 palabras, palabras de un bit, que por lo tanto tiene 6 bits de dirección, y que por lo tanto, sabemos, que son capaces de implementar cualquier función booleana de seis variables. Supongamos que ahora lo que queremos es implementar una función de 8 variables, ¿cómo podemos hacerlo?. Pués, lo que podemos es aplicar el Teorema de Shannon dos veces, en dos iteraciones. de manera que, aplicándolo la primera vez habríamos pasado de una función de 8 variables, que es la que queremos construir, a dos funciones de 7 variables, y ahora podríamos volverlo a aplicar aquí, y obtener 4 funciones que ahora dependerán de 6 variables. Aquí tenemos la función de 8 variables. Estas funciones, (5) y (6), son funciones de 7 variables, como véis aquí. Aplicando por segunda vez el Teorema de Shannon, llegamos a éstas 4 funciones, (1), (2), (3), (4) que dependen solo de 6 variables, y que por lo tanto ya pueden implementarse directamente con éstas pequeñas memorias ROM, con éstas LUTs. Éste circuito, como véis, combinando LUTs de 6 variables, más multiplexores, permite construir, una función de 8 variables. Evidentemente, ésto se puede extender a cualquier número de variables. De hecho, este circuito admite otra implementación ... fijémonos que esta parte, formada por los multiplexores, es un bloque al cual entran 1, 2, 3, 4 tres, entradas, que son las salidas de las LUTs, más x1 y x0. Es decir, en el fondo este conjunto de trés multiplexores implementa una función de 6 variables, y por lo tanto, este bloque puede a su vez ser sustituido por una LUT. Ésto lo vemos, en el dibujo siguiente. ¿Véis? Hemos sustituido los multiplexores por una LUT. Bien, pués así es como se trabaja con memorias ROM más multiplexores. Os dejo un quiz, para ver si habéis comprendido, todos estos conceptos.