viernes, 26 de junio de 2015

El nivel de microarquitectura
El nivel que esta arriba de lógica digital es el de la microarquitectura. Su tarea es implementar el nivel ISA (arquitectura de conjunto de instrucciones) que esta arriba de el. El diseño del nivel de microarquitectura depende de la ISA que se esta implementando, asi como los objetivos de costo y desempeño de la computadora. Muchas ISA modernas, en especial los diseños RISC, tienen instrucciones sencillas que por lo regular pueden ejecutarse en un solo ciclo de reloj. Las ISA mas complejas, como la de Pentium II, podrían requerir muchos ciclos para ejecutar una sola instrucción. Dicha ejecución podría requerir encontrar los operandos en  la memoria, leerlos y almacenar resultados de vuelta en la memoria. La secuencia de operaciones dentro de una sola instrucción con frecuencia obliga a adoptar un diferente enfoque de control que con las ISA sencillas.
4.1 Un ejemplo de microarquitectura
Lo ideal sería comenzar explicando los principios generales del diseño de microarquitecturas. Lo malo es que no existen principios generales; cada una es un caso especial. Por tanto, optaremos por analizar un ejemplo detallado. Como ejemplo de ISA hemos escogido un subconjunto de la maquina virtual Java. Este subconjunto contiene solo instrucciones para manejar enteros por lo que lo hemos llamado IJVM (Integer Java Virtual Machine).
Comenzaremos por describir la microarquitectura sobre la cual implementaremos IJVM. La IJVM tiene algunas instrucciones relativamente complejas. Muchas arquitecturas de este tipo se han implementado con microprogramación. Aunque IJVM es pequeña, es un buen punto de partida para describir el control y las secuencias de las instrucciones.
Nuestra microarquitectura contendrá un microprograma (en ROM) cuya tarea es buscar, decodificar y ejecutar instrucciones IJVM. No podemos usar el interprete de JVM de Sun en vez del microprograma porque necesitamos un microprograma diminuto que alimente de forma eficiente las compuertas individuales del hardware real. El interprete de JVM de Sun, en cambio, se escribió en C para ser transportable, y no puede controlar el hardware con el nivel de detalle que necesitamos. Puesto que el hardware real empleado consite únicamente en los componentes básicos que describimos en el capitulo 3, en teoría, después de entender plenamente este capitulo, el lector deberá poder salir y comprar una gran bolsa llena de transistores, y construir este subconjunto de la maquina JVM. Un modelo conveniente para el diseño de la microarquitectura es ver el diseño como un problema de programación, en el que cada instrucción en el nivel ISA es una función que el programa maestro debe invocar. En este modelo el programa maestro es un ciclo infinito simple que determina cual función se invocara, llama a la función, y vuelve a comenzar.
El microprograma tiene un conjunto de variables, llamadas estados de la computadora, al que todas las funciones tienen acceso. Cada función modifica al menos una de las variables que constituyen el estado. Por ejemplo, el contador de programa (PC) forma parte del estado; indica la localidad de memoria que contiene la siguiente función (o sea, instrucción de ISA) que se ejecutará. Durante la ejecución de cada instrucción el PC se incrementa para apuntar a la siguiente instrucción por ejecutar.
Las instrucciones de IJVM son cortas y precisas. Cada instrucción tiene unos cuantos campos, por lo regular uno o dos, cada uno de los cuales tiene un propósito especifico. El primer campo de cada instrucción es el código de operación (opcode) que identifica la instrucción como, por ejemplo, ADD (sumar) o BRANCH (ramificar) o algo mas. Muchas instrucciones tienen un campo adicional que especifica el operando. Por ejemplo, las instrucciones que accesan una variable local necesitan un campo para indicar cual variable. Este modelo de ejecución, llamado ciclo de búsqueda-ejecucion, es útil en lo abstracto y también podría ser la base para implementar ISA como IJVM que tienen instrucciones complejas.
La trayectoria de datos es la parte de la CPU que contiene la ALU, sus entradas y sus salidas. La trayectoria de datos de nuestro ejemplo de microarquitectura se muestra en la figura 4-1. Si bien esta trayectoria de datos se optimó minuciosamente para interpretar pro­gramas en IJVM, es muy similar al que se usa en la mayor parte de las máquinas. Esta trayectoria contiene varios registros de 32 bits, a los que hemos asignado nombres simbólicos como PC, SP y MDR. Aunque algunos de estos nombres son conocidos, es importante entender que estos registros pueden accesarse sólo en el nivel de microarquitectura (por el micro- programa). Reciben estos nombres porque casi siempre contienen un valor que corresponde a la variable del mismo nombre en la arquitectura del nivel ISA. Casi todos los registros pueden colocar su contenido en el bus B. La salida de la ALU alimenta al desplazador y luego al bus C, cuyo valor puede escribirse en uno o más registros al mismo tiempo. No existe un bus A por el momento; lo añadiremos después.
La ALU es idéntica a la que se muestra en la figura 3-19 y la figura 3-20. Su función está determinada por seis líneas de control. La línea diagonal corta rotulada con “6” en la figura 4-1 indica que hay seis líneas de control de la ALU. Éstas son F0 y F, para determinar el funcio­namiento de la ALU, ENA y ENB para habilitar individualmente las salidas, INVA para invertir la entrada izquierda, e INC para forzar un acarreo de entrada en el bit de orden bajo, lo que en realidad suma 1 al resultado. Sin embargo, no todas las 64 combinaciones de las líneas de control de la ALU hacen algo útil.
Algunas de las combinaciones más interesantes se muestran en la figura 4-2. No todas estas funciones se necesitan en la IJVM, pero muchas de ellas serían útiles en la JVM com­pleta. En muchos casos hay varias posibilidades para obtener el mismo resultado. En esta tabla, + significa suma aritmética y -significa resta aritmética, de modo que, por ejemplo, -A indica el complemento a 2 de A.
La ALU de la figura 4-1 necesita dos entradas de datos: una entrada izquierda (A) y una entrada derecha (B). La entrada izquierda tiene conectado un registro de retención, H. La entrada derecha está conectada al bus B, que puede cargarse desde cualquiera de nueve oríge­nes, como indican las nueve flechas grises que lo tocan. Un diseño alternativo, con dos buses completos, tiene un conjunto diferente de ventajas y desventajas, mismas que veremos más adelante en este capítulo.
pueden colocar su contenido en el bus B. La salida de la ALU alimenta al desplazador y luego al bus C, cuyo valor puede escribirse en uno o más registros al mismo tiempo. No existe un bus A por el momento; lo añadiremos después.
La ALU es idéntica a la que se muestra en la figura 3-19 y la figura 3-20. Su función está determinada por seis líneas de control. La línea diagonal corta rotulada con “6” en la figura 4-1 indica que hay seis líneas de control de la ALU. Éstas son F0 y F, para determinar el funcio­namiento de la ALU, ENA y ENB para habilitar individualmente las salidas, INVA para invertir la entrada izquierda, e INC para forzar un acarreo de entrada en el bit de orden bajo, lo que en realidad suma 1 al resultado. Sin embargo, no todas las 64 combinaciones de las líneas de control de la ALU hacen algo útil.
Algunas de las combinaciones más interesantes se muestran en la figura 4-2. No todas estas funciones se necesitan en la IJVM, pero muchas de ellas serían útiles en la JVM com­pleta. En muchos casos hay varias posibilidades para obtener el mismo resultado. En esta tabla, + significa suma aritmética y -significa resta aritmética, de modo que, por ejemplo, -A indica el complemento a 2 de A.
La ALU de la figura 4-1 necesita dos entradas de datos: una entrada izquierda (A) y una entrada derecha (B). La entrada izquierda tiene conectado un registro de retención, H. La entrada derecha está conectada al bus B, que puede cargarse desde cualquiera de nueve oríge­nes, como indican las nueve flechas grises que lo tocan. Un diseño alternativo, con dos buses completos, tiene un conjunto diferente de ventajas y desventajas, mismas que veremos más adelante en este capítulo.
H se puede cargar inicialmente escogiendo una función de la ALU que se limite a pasar la entrada derecha (del bus B) hasta la salida de la ALU. Una función así sería sumar las entradas de la ALU, sólo que con EN A deshabilitada para que la entrada izquierda sea forzo­samente cero. Sumar cero al valor del bus B produce ese mismo valor. Luego, este resultado puede pasarse por el desplazador sin modificación y guardarse en H.
Además de las funciones anteriores, se pueden usar otras dos líneas de control de forma independiente para controlar la salida de la ALU. SLL8 (desplazamiento lógico a la izquier­da, Shift Left Logical) desplaza el contenido un byte a la izquierda, llenando los ocho bits menos significativos con ceros. SRA1 (desplazamiento aritmético a la derecha, Shift Right Arithmetic) desplaza el contenido un bit a la derecha, sin modificar el bit más significativo.
Es posible explícitamente leer y escribir el mismo registro en un ciclo. Por ejemplo, está permitido colocar SP en el bus B, deshabilitar la entrada izquierda de la ALU, habilitar la señal INC y guardar el resultado en SP con lo que se incrementa SP en 1 (vea la octava línea de la figura 4-2). ¿Cómo puede leerse y escribirse un registro en el mismo ciclo sin producir basura? La solución es que la lectura y la escritura se realizan realmente en diferentes mo­mentos dentro del ciclo. Cuando un registro se selecciona como entrada derecha de la ALU, su valor se coloca en el bus B al principio del ciclo y se mantiene ahí continuamente durante todo el ciclo. Luego, la ALU efectúa su trabajo y produce un resultado que pasa al bus C a través del desplazador. Cerca del final del ciclo, cuando se sabe que las salidas de la ALU y del desplazador están estables, una señal de reloj activa el almacenamiento del bus C en uno más de los registros. Uno de estos registros bien podría ser el que proporcionó su entrada al bus B. La temporización precisa de la trayectoria de datos hace posible leer y escribir el mismo registro en un ciclo, como se describe en seguida.
Temporización de la trayectoria de datos
La temporización de estos sucesos se muestra en la figura 4-3. Aquí se produce una pulsación corta al principio de cada ciclo de reloj. La pulsación se puede sacar del reloj principal, como se muestra en la figura 3-21(c). En el flanco descendente de la pulsación, ya están preparados los bits que alimentarán todas las compuertas. Esto tarda un tiempo finito conocido, Aw. Luego se selecciona el registro que se necesita en el bus B y su valor se pasa a ese bus. Se requiere un tiempo Ax para que el valor se estabilice. Luego la ALU y el desplazador comien­zan a operar con datos válidos. Después de un tiempo Ay adicional, las salidas de la ALU y el desplazador se han estabilizado. Después de otro intervalo Az, los resultados se han propaga­do por el bus C hasta los registros, en los que pueden cargarse durante el flanco ascendente de la siguiente pulsación. La carga debe ser disparada por flanco y rápida, de modo que aun si algunos de los registros de entrada se modifican, los efectos no se harán sentir en el bus C sino hasta mucho después de haberse cargado todos los registros. También en el flanco as­cendente de la pulsación, el registro que alimenta al bus B deja de hacerlo, en preparación del siguiente ciclo. MPC, MIR y la memoria se mencionan en la figura; exploraremos sus pape­les en breve.
Es importante darse cuenta de que aunque no hay elementos de almacenamiento en la trayectoria de datos, hay un tiempo de propagación finito a través suyo. Modificar el valor que está en el bus B no hace que el bus C cambie sino hasta después de un tiempo finito (debido a los retrasos finitos en cada paso). Por consiguiente, aun si una operación de alma­cenamiento modifica uno de los registros de entrada, el valor ya estará guardado a salvo en el registro mucho antes de que el valor (ahora incorrecto) que se está colocando en el bus B (o en H) pueda llegar a la ALU.
Para que este diseño funcione se requiere una temporización rígida, un ciclo de reloj largo, un tiempo de propagación mínimo conocido a través de la ALU y un cargado rápido de los registros desde el bus C. Sin embargo, con una ingeniería cuidadosa se puede diseñar la trayectoria de datos de modo que funcione correctamente.
Una forma un tanto distinta de ver la trayectoria de datos es dividirlo conceptualmente en subciclos implícitos. El inicio del subciclo 1 se dispara por el flanco descendente del reloj. A continuación mostramos las actividades que ocurren durante los subciclos, junto con las longitudes de los subciclos (entre paréntesis).
1.       Se preparan las señales de control (Aw).
2.       Los registros se cargan en el bus B (Ax).
3.       La ALU y el desplazador operan (Ay).
4.       Los resultados se propagan por el bus C de regreso a los registros (Az).
En el flanco ascendente del siguiente ciclo de reloj, los resultados se almacenan en los registros.
Dijimos que la mejor forma de ver los subciclos es como algo implícito. Con esto quere­mos decir que no hay pulsaciones de reloj ni otras señales implícitas que indiquen a la ALU cuándo debe operar o que digan a los resultados que entren en el bus C. En realidad, la ALU y que está en el bus B no hace que el bus C cambie sino hasta después de un tiempo finito (debido a los retrasos finitos en cada paso). Por consiguiente, aun si una operación de alma­cenamiento modifica uno de los registros de entrada, el valor ya estará guardado a salvo en el registro mucho antes de que el valor (ahora incorrecto) que se está colocando en el bus B (o en H) pueda llegar a la ALU.
Para que este diseño funcione se requiere una temporización rígida, un ciclo de reloj largo, un tiempo de propagación mínimo conocido a través de la ALU y un cargado rápido de los registros desde el bus C. Sin embargo, con una ingeniería cuidadosa se puede diseñar la trayectoria de datos de modo que funcione correctamente.
Una forma un tanto distinta de ver la trayectoria de datos es dividirlo conceptualmente en subciclos implícitos. El inicio del subciclo 1 se dispara por el flanco descendente del reloj. A continuación mostramos las actividades que ocurren durante los subciclos, junto con las longitudes de los subciclos (entre paréntesis).
1.       Se preparan las señales de control (Aw).
2.       Los registros se cargan en el bus B (Ax).
3.       La ALU y el desplazador operan (Ay).
4.       Los resultados se propagan por el bus C de regreso a los registros (Az).
En el flanco ascendente del siguiente ciclo de reloj, los resultados se almacenan en los registros.
Dijimos que la mejor forma de ver los subciclos es como algo implícito. Con esto quere­mos decir que no hay pulsaciones de reloj ni otras señales implícitas que indiquen a la ALU cuándo debe operar o que digan a los resultados que entren en el bus C. En realidad, la ALU y
el desplazador están operando todo el tiempo. Sin embargo, sus entradas son basura hasta un tiempo
Aw + Ax después del flanco descendente del reloj. De igual manera, sus salidas son basura hasta que ha transcurrido Aw + Ax + Ay después del flanco descendente del reloj. Las únicas señales explícitas que controlan la trayectoria de datos son el flanco descendente del reloj, que inicia el ciclo de la trayectoria de datos, y el flanco ascendente del reloj, que carga los registros a partir del bus C. Las otras fronteras de los subciclos están determinadas implí­citamente por los tiempos de propagación inherentes a los circuitos participantes. Es respon­sabilidad de los ingenieros de diseño asegurarse de que el tiempo Aw + Ax + Ay + Az termine con tiempo de sobra antes del flanco ascendente del reloj, si se quiere que el cargado de los registros funcione siempre.
Operación de la memoria
Nuestra máquina tiene dos formas diferentes de comunicarse con la memoria: un puerto de memoria de 32 bits direccionable por palabra y un puerto de memoria de 8 bits direccionable por byte. El puerto de 32 bits está bajo el control de dos registros, MAR (registro de direc­ción de memoria, Memory Address Register) y MDR (registro de datos de memoria, Memory Data Register), como se muestra en la figura 4-1. El puerto de ocho bits está bajo el control de un registro, PC, que lee un byte y lo coloca en los ocho bits de orden bajo de MBR. Este puerto sólo puede leer datos de la memoria; no puede escribir datos en la memoria.
Cada uno de estos registros (y todos los demás registros de la figura 4-1) operan con una o dos señales de control. Una flecha blanca bajo un registro indica una señal de control que habilita la colocación de la salida del registro en el bus B. Puesto que MAR no está conectado con el bus B, no tiene una señal de habilitación. H tampoco tiene una porque siempre está habilitado, al ser la única posible entrada izquierda de la ALU.
Una flecha negra bajo un registro indica una señal de control que escribe (es decir, carga) el registro a partir del bus C. Puesto que MBR no se puede cargar a partir del bus C, no tiene una señal de escritura (aunque sí tiene otras dos señales de habilitación, que se describirán más adelante). Para iniciar una lectura o escritura de memoria, es preciso cargar los registros de memoria apropiados, y luego emitir una señal de lectura o escritura a la memoria (no se muestra en la figura 4-1).
MAR contiene direcciones de palabra, de modo que los valores 0,1,2, etc., se refieren a palabras consecutivas. PC contiene direcciones de byte, de modo que los valores 0,1,2, etc., se refieren a bytes consecutivos. Así, colocar un 2 en PC e iniciar una lectura de memoria hará que se lea el byte 2 de la memoria y se coloque en los 8 bits de orden bajo de MBR. Colocar un 2 en MAR e iniciar una lectura de memoria hará que se lean los bytes 8-11 (o sea, la palabra 2) de la memoria y se coloquen en MDR.
Esta diferencia en funcionalidad es necesaria porque MAR y PC se usarán para hacer referencia a dos partes diferentes de la memoria. La necesidad de esta distinción se aclarará posteriormente. Por ahora, basta decir que la combinación MAR/MDR se usa para leer y escribir palabras de datos en el nivel ISA, y la combinación PC/MBR sirve para leer el programa ejecutable en el nivel ISA, que consiste en un flujo de bytes. Todos los demás registros que contienen direcciones emplean direcciones de palabra, como MAR.
En la implementación física real, sólo hay una memoria y está organizada en bytes. Un sencillo truco permite a MAR contar en palabras (lo cual es necesario debido a la forma en que se definió la JVM) mientras la memoria física cuenta en bytes. Cuando MAR se coloca en el bus de direcciones, sus 32 bits no se corresponden directamente con las 32 líneas de dirección, 0-31. En vez de ello, el bit 0 de MAR se conecta con la línea 2 del bus de direccio­nes, el bit 1 de MAR se conecta con la línea 3 del bus de direcciones, y así. Los 2 bits superiores de MAR se desechan porque sólo se necesitan para las direcciones de palabra mayores que 232, que no son válidas en nuestra máquina de 4 GB. Con esta transformación, cuando MAR es 1, se coloca la dirección 4 en el bus; cuando MAR es 2, se coloca la dirección 8 en el bus, y así. Este truco se ilustra en la figura 4-4.

Como ya se mencionó, los datos leídos de la memoria a través del puerto de memoria de ocho bits se devuelven en MBR, un registro de 8 bits. MBR puede copiarse en el bus B de una de dos formas: sin signo o con signo. Cuando se necesita un valor sin signo, la palabra de 32 bits que se coloca en el bus B contiene el valor de MBR en los 8 bits de orden bajo y ceros en los 24 bits superiores. Los valores sin signo son útiles como índices de tablas, o cuando es preciso armar un entero de 16 bits con dos bytes (sin signo) consecutivos del flujo de instrucciones.
La otra opción para convertir el MBR de 8 bits en una palabra de 32 bits es tratarlo como un valor con signo entre -128 y +127 y usar este valor para generar una palabra de 32 bits que tenga el mismo valor numérico. Esta conversión se efectúa copiando el bit de signo de MBR (el bit de la extrema izquierda) en las 24 posiciones de bit superiores del bus B, proceso que se llama extensión del signo. Cuando se escoge esta opción, los 24 bits superiores serán todos ceros o todos unos, dependiendo de si el bit de la izquierda del MBR es 0 o 1.
La decisión de convertir el MBR de 8 bits en un valor de 32 bits con o sin signo en el bus B depende de cuál de las dos líneas de control (flechas blancas debajo de MBR en la figura 4-1) está habilitada. Hay dos flechas porque se necesitan estas dos opciones. La capacidad de hacer que el MBR de 8 bits actúe como una fuente de 32 bits para el bus B se indica con el rectángulo punteado a la izquierda de MBR en la figura.

En la implementación física real, sólo hay una memoria y está organizada en bytes. Un sencillo truco permite a MAR contar en palabras (lo cual es necesario debido a la forma en que se definió la JVM) mientras la memoria física cuenta en bytes. Cuando MAR se coloca en el bus de direcciones, sus 32 bits no se corresponden directamente con las 32 líneas de dirección, 0-31. En vez de ello, el bit 0 de MAR se conecta con la línea 2 del bus de direccio­nes, el bit 1 de MAR se conecta con la línea 3 del bus de direcciones, y así. Los 2 bits superiores de MAR se desechan porque sólo se necesitan para las direcciones de palabra mayores que 232, que no son válidas en nuestra máquina de 4 GB. Con esta transformación, cuando MAR es 1, se coloca la dirección 4 en el bus; cuando MAR es 2, se coloca la dirección 8 en el bus, y así. Este truco se ilustra en la figura 4-4.
Como ya se mencionó, los datos leídos de la memoria a través del puerto de memoria de ocho bits se devuelven en MBR, un registro de 8 bits. MBR puede copiarse en el bus B de una de dos formas: sin signo o con signo. Cuando se necesita un valor sin signo, la palabra de 32 bits que se coloca en el bus B contiene el valor de MBR en los 8 bits de orden bajo y ceros en los 24 bits superiores. Los valores sin signo son útiles como índices de tablas, o cuando es preciso armar un entero de 16 bits con dos bytes (sin signo) consecutivos del flujo de instrucciones.
La otra opción para convertir el MBR de 8 bits en una palabra de 32 bits es tratarlo como un valor con signo entre -128 y +127 y usar este valor para generar una palabra de 32 bits que tenga el mismo valor numérico. Esta conversión se efectúa copiando el bit de signo de MBR (el bit de la extrema izquierda) en las 24 posiciones de bit superiores del bus B, proceso que se llama extensión del signo. Cuando se escoge esta opción, los 24 bits superiores serán todos ceros o todos unos, dependiendo de si el bit de la izquierda del MBR es 0 o 1.
La decisión de convertir el MBR de 8 bits en un valor de 32 bits con o sin signo en el bus B depende de cuál de las dos líneas de control (flechas blancas debajo de MBR en la figura 4-1) está habilitada. Hay dos flechas porque se necesitan estas dos opciones. La capacidad de hacer que el MBR de 8 bits actúe como una fuente de 32 bits para el bus B se indica con el rectángulo punteado a la izquierda de MBR en la figura.
4.1.2     Microinstrucciones
Para controlar la trayectoria de datos necesitamos 29 señales. Éstas se pueden dividir en cinco grupos funcionales, como se describe a continuación.
9 Señales para controlar la escritura de datos del bus C en registros.
9 Señales para controlar la habilitación de registros en el bus B para introducción en la ALU.
8 Señales para controlar las funciones de la ALU y el desplazador.
2 Señales (que no se muestran) para indicar lectura/escritura de memoria vía MAR/MDR.
1 Señal (que no se muestra) para indicar obtención de memoria vía PC/MBR.

Los valores de estas 29 señales de control especifican las operaciones durante un ciclo de la trayectoria de datos. Un ciclo consiste en colocar valores de los registros en el bus B, propa­gar las señales a través de la ALU y el desplazador, alimentarlas al bus C y por último escribir los resultados en el registro o registros apropiados. Además, si una señal de lectura de datos de memoria está habilitada, la operación de memoria se inicia al final del ciclo de la trayecto­ria de datos, una vez que se ha cargado MAR. Los datos de memoria están disponibles hasta el final del siguiente ciclo enMBRoMDRyse usa el ciclo que sigue. En otras palabras, una lectura de memoria por cualquiera de los puertos, iniciada al final del ciclo k, proporciona datos que no se usan en el ciclo k + 1, sino hasta el ciclo k + 2 o uno posterior.
Este comportamiento que aparentemente va contra la intuición se explica con la figura 4-3. Las señales de control de la memoria no se generan en el ciclo de reloj 1 sino hasta inmediatamente después de que MAR y PC se cargan en el flanco ascendente del reloj, hacia el final del ciclo de reloj 1. Supondremos que la memoria coloca sus resultados en los buses de memoria en el plazo de un ciclo, de modo que MBR y/o MDR se puedan cargar en el siguien­te flanco ascendente del reloj, junto con los demás registros.
En otras palabras, cargamos MAR al final de un ciclo de la trayectoria de datos e inicia­mos la lectura de memoria poco tiempo después. Por tanto, no podemos esperar realmente que los resultados de una operación de lectura estén en MDR al principio del siguiente ciclo, sobre todo si la pulsación de reloj es angosta. Si la memoria tarda un ciclo de reloj, simple­mente no hay suficiente tiempo. Debe transcurrir un ciclo de la trayectoria de datos completa entre que se inicia una lectura de memoria y que se usa el resultado. Desde luego, se efectúan otras operaciones durante ese ciclo, pero no las que necesitan la palabra de la memoria.
El supuesto de que la memoria tarda un ciclo en operar equivale a suponer que la tasa de aciertos en caché es de 100%. Este supuesto nunca se cumple, pero la complejidad que implica tener un ciclo de memoria de longitud variable es mayor que con la que queremos lidiar aquí.
Puesto que MBR y MDR se cargan en el flanco ascendente del reloj, junto con todos los demás registros, podrían leerse durante ciclos en los que se está efectuando una nueva lectura de memoria. Esa lectura devuelve los valores antiguos, ya que todavía no ha habido tiempo de sobreescribirlos. No hay ambigüedad aquí; hasta que se carguen valores nuevos en MBR y MDR en el flanco ascendente del reloj, estos registros contendrán sus valores anteriores, los cuales se podrán usar. Cabe señalar que es posible realizar lecturas una tras otra en dos ciclos consecutivos puesto que una lectura sólo tarda un ciclo. Además, ambas memorias podrían operar al mismo tiempo. Sin embargo, tratar de leer y escribir el mismo byte simul­táneamente produce resultados no definidos.
Si bien podría ser deseable escribir la salida colocada en el bus C en más de un registro, nunca es conveniente habilitar más de un registro para el bus B al mismo tiempo. (De hecho, algunas implementaciones reales sufren daños físicos si se hace esto.) Si se agregan unos circuitos más, podemos reducir el número de bits necesarios para seleccionar una de las posibles fuentes que alimentan al bus B. Sólo hay nueve registros de entrada que pueden alimentar al bus B (si contamos por separado las versiones con signo y sin signo de MBR). Por tanto, podemos codificar la información del bus B en cuatro bits y usar un decodificador para generar las 16 señales de control, siete de las cuales no se necesitan. En un diseño comercial, los arquitectos estarían fuertemente tentados a deshacerse de uno de los registros para poder efectuar la tarea con sólo 3 bits. Como académicos, podemos damos el lujo de desperdiciar un bit para contar con un diseño más limpio y sencillo.
A estas alturas podemos controlar la trayectoria de datos con 9 + 4 + 8 + 2+ 1 =24 señales, y por tanto 24 bits. Sin embargo, estos 24 bits sólo controlan la trayectoria de datos durante un ciclo. La segunda parte del control consiste en determinar qué debe hacerse en el siguiente ciclo. Para incluir esto en el diseño del controlador, crearemos un formato para describir las operaciones que se efectuarán usando los 24 bits de control más dos campos adicionales: el campo NEXT_ADDRESS y el campo JAM. A continuación explicaremos el contenido de cada uno de estos campos. La figura 4-5 muestra un posible formato, dividido en los seis grupos, y que contiene las 36 señales siguientes:

los cuales se podrán usar. Cabe señalar que es posible realizar lecturas una tras otra en dos ciclos consecutivos puesto que una lectura sólo tarda un ciclo. Además, ambas memorias podrían operar al mismo tiempo. Sin embargo, tratar de leer y escribir el mismo byte simul­táneamente produce resultados no definidos.
Si bien podría ser deseable escribir la salida colocada en el bus C en más de un registro, nunca es conveniente habilitar más de un registro para el bus B al mismo tiempo. (De hecho, algunas implementaciones reales sufren daños físicos si se hace esto.) Si se agregan unos circuitos más, podemos reducir el número de bits necesarios para seleccionar una de las posibles fuentes que alimentan al bus B. Sólo hay nueve registros de entrada que pueden alimentar al bus B (si contamos por separado las versiones con signo y sin signo de MBR). Por tanto, podemos codificar la información del bus B en cuatro bits y usar un decodificador para generar las 16 señales de control, siete de las cuales no se necesitan. En un diseño comercial, los arquitectos estarían fuertemente tentados a deshacerse de uno de los registros para poder efectuar la tarea con sólo 3 bits. Como académicos, podemos damos el lujo de desperdiciar un bit para contar con un diseño más limpio y sencillo.
A estas alturas podemos controlar la trayectoria de datos con 9 + 4 + 8 + 2+ 1 =24 señales, y por tanto 24 bits. Sin embargo, estos 24 bits sólo controlan la trayectoria de datos durante un ciclo. La segunda parte del control consiste en determinar qué debe hacerse en el siguiente ciclo. Para incluir esto en el diseño del controlador, crearemos un formato para describir las operaciones que se efectuarán usando los 24 bits de control más dos campos adicionales: el campo NEXT_ADDRESS y el campo JAM. A continuación explicaremos el contenido de cada uno de estos campos. La figura 4-5 muestra un posible formato, dividido en los seis grupos, y que contiene las 36 señales siguientes:
Dir - Contiene la dirección de una microinstrucción que podría ser la siguiente.
JAM - Determina cómo se selecciona la siguiente microinstrucción.
ALU - Funciones de la ALU y el desplazador.
C - Selecciona cuáles registros se escriben desde el bus C.
Mem - Funciones de memoria.
B - Selecciona la fuente del bus B; se codifica como se muestra.
El ordenamiento de los grupos es arbitrario en principio, aunque en realidad se escogió con mucho cuidado a fin de minimizar los cruces de líneas en la figura 4-6. Los cruces de línea en diagramas esquemáticos como el de la figura 4-6 a menudo corresponden a cruces de hilos en chips, que pueden causar problemas en los diseños bidimensionales y deben evitarse.
4.1.3 Control de microinstrucciones: el Mic-1
Hasta aquí hemos descrito cómo se controla la trayectoria de datos, pero todavía no hemos explicado cómo se decide cuál de las señales de control debe estar habilitada en cada ciclo. Esto lo determina un secuenciador que se encarga de recorrer la secuencia de operaciones necesarias para ejecutar una sola instrucción ISA.
El secuenciador debe producir dos tipos de información en cada ciclo:
1.       El estado de cada señal de control en el sistema.
2.       La dirección de la microinstrucción que debe ejecutarse a continuación.
La figura 4-6 es un diagrama de bloques detallado de la microarquitectura completa de nuestra máquina de ejemplo, que llamaremos Mic-1. A primera vista este diagrama podría parecer abrumador, pero vale la pena estudiarlo con detenimiento. Una vez que entienda plenamente todos y cada uno de los bloques y líneas de esta figura, habrá adelantado mucho en la comprensión del nivel de microarquitectura. El diagrama de bloques tiene dos partes: la trayectoria de datos, a la izquierda, que ya vimos con detalle, y la sección de control, a la derecha, que examinaremos a continuación.
El elemento más grande e importante de la porción de control de la máquina es una memo­ria llamada almacén de control. Es recomendable ver este “almacén” como una memoria que contiene todo el microprograma, aunque a veces se implementa como un conjunto de compuer­tas lógicas. En general, nos referiremos a él como almacén de control para evitar confun­dirlo con la memoria principal, a la que se accede a través de MBR y MDR. Sin embargo, en lo funcional, el almacén de control no es más que una memoria que contiene microinstrucciones en lugar de instrucciones ISA. En nuestra máquina de ejemplo, el almacén contiene 512 pala­bras, cada una de las cuales consiste en una microinstrucción de 36 bits como las que se ilustran en la figura 4-5. En realidad, no se necesitan todas estas palabras pero (por razones que se explicarán en breve) necesitamos direcciones para 512 palabras distintas.
Es importante notar que el almacén de control es muy diferente de la memoria principal: las instrucciones de la memoria principal tienen la propiedad de que se ejecutan en orden según su dirección (con excepción de las ramificaciones); las microinstrucciones no. El acto de incrementar el contador de programa en la figura 2-3 expresa el hecho de que la instruc­ción que se ejecutará por omisión después de la actual es la que sigue a la actual en la memoria. Los microprogramas necesitan mayor flexibilidad (porque las secuencias de microinstrucciones tienden a ser cortas), así que generalmente r.o tienen esta propiedad. En lugar de esto, cada microinstrucción especifica explícitamente su sucesora.

según su dirección (con excepción de las ramificaciones); las microinstrucciones no. El acto de incrementar el contador de programa en la figura 2-3 expresa el hecho de que la instruc­ción que se ejecutará por omisión después de la actual es la que sigue a la actual en la memoria. Los microprogramas necesitan mayor flexibilidad (porque las secuencias de microinstrucciones tienden a ser cortas), así que generalmente r.o tienen esta propiedad. En lugar de esto, cada microinstrucción especifica explícitamente su sucesora.
Puesto que el almacén de control es funcionalmente una memoria (sólo de lectura), ne­cesita su propio registro de direcciones de memoria y su propio registro de datos de memoria. No necesita señales de leer y escribir, porque continuamente se está leyendo. Llamaremos al registro de dirección de memoria del almacén de control MPC (contador de microprograma, MicroProgram Counter). Este nombre es irónico porque las localidades que contiene no es­tán ordenadas, explícitamente, de modo que el concepto de contar no es útil (pero, ¿quiénes somos nosotros para discutir con la tradición?). El registro de datos de memoria se llama MIR (registro de microinstrucción, Microlnstruction Register). Su función es almacenar la microinstrucción actual, cuyos bits alimentan las señales de control que operan la trayecto­ria de datos.
El registro MIR de la figura 4-6 contiene los mismos seis grupos de la figura 4-5. Los grupos Dir y J (por JAM) controlan la selección de la siguiente microinstrucción y se estudia­rán más adelante. El grupo ALU contiene ocho bits que seleccionan la función de la ALU y controlan el desplazador. Los bits C hacen que registros individuales carguen la salida de la ALU desde el bus C. Los bits M controlan las operaciones de memoria.
Por último, los cuatro bits finales controlan el decodificador que determina qué se coloca en el bus B. En este caso hemos escogido un decodificador estándar 4 a 16, aunque sólo se requieren nueve posibilidades. En un diseño más afinado se usaría un decodificador de 4 a 9. La ventaja aquí es poder usar un circuito estándar tomado de una biblioteca de circuitos en lugar de diseñar uno a la medida. El uso del circuito estándar es más fácil y reduce la posibi­lidad de introducir errores. Crear circuitos propios ocupa menos área de chip pero tarda más y podríamos tener errores.
La operación de la figura 4-6 es como sigue. Al principio de cada ciclo de reloj (el flanco descendente del reloj de la figura 4-3), MIR se carga con la palabra del almacén de control a la que MPC apunta. El tiempo de carga de MIR se indica en la figura con Aw. Si pensamos en términos de subciclos, MIR se carga durante el primero.
Una vez que la microinstrucción está en MIR, las diversas señales se propagan a la tra­yectoria de datos. Se coloca un registro en el bus B, la ALU sabe qué instrucción debe ejecu­tar y ocurren muchas actividades más. Éste es el segundo subciclo. Después de un intervalo Aw + Ax a partir del principio del ciclo, las entradas de la ALU se han estabilizado.
Después de transcurrir un tiempo Ay adicional, todo se ha calmado otra vez y las salidas de la ALU, N, Z y el desplazador están estables. Luego, los valores de N y Z se guardan en un par de flip-flops de un bit. Estos bits, al igual que todos los registros que se cargan a partir del bus C y la memoria, se guardan en el flanco ascendente del reloj, cerca del final del ciclo de la trayectoria de datos. La salida de la ALU no se almacena, sólo se alimenta al desplazador. La actividad de la ALU y el desplazador ocurre durante el subciclo 3.
Después de un intervalo adicional, Az, la salida del desplazador ha llegado a los registros por el bus C. Luego los registros pueden cargarse cerca del final del ciclo (en el flanco ascen­dente de la pulsación de reloj de la figura 4-3). El subciclo 4 consiste en el cargado de los registros y los flip-flops N y Z; termina un poco después del flanco ascendente del reloj, cuando todos los resultados se han guardado y los resultados de las operaciones de memoria anteriores están disponibles, y se ha cargado MPC. Este proceso continúa hasta que alguien se aburre y apaga la máquina.
Al mismo tiempo que controla la trayectoria de datos, el microprograma tiene que deter­minar qué microinstrucción ejecutará a continuación, porque no se ejecutan en el orden en que aparecen en el almacén de control. El cálculo de la dirección de la siguiénte microins­trucción se inicia una vez que MIR se ha cargado y estabilizado. Primero se copia el campo NEXT_ADDRESS de 9 bits en MPC. Mientras se está efectuando este copiado, se inspec­ciona el campo JAM. Si tiene el valor 000, no se hace nada más; una vez que concluye el copiado de NEXT_ADDRESS, MPC apunta a la siguiente microinstrucción.
Si uno o más de los bits de JAM son 1, hay que trabajar más. Si JAMN está activado, el flip-flop N de un bit se hace OR con el bit de orden alto de MPC. Del mismo modo, si JAMZ está establecido, el flip-flop Z de un bit realiza la operación OR. Si ambos están establecidos, se realiza la operación OR de ellos. Se necesitan los flip-flops N y Z porque después del flanco ascendente del reloj (mientras el reloj está alto), el B bus ya no está siendo controlado, y no puede suponerse que las salidas de la ALU sigan siendo correctas. Guardar las banderas de estado de la ALU en N y Z hace que los valores correctos se estabilicen y estén disponibles para el cálculo de MPC, sin importar qué esté ocurriendo en tomo de la ALU.
En la figura 4-6 la lógica que efectúa este cálculo se denomina “Bit alto”. La función booleana que calcula es
F= (JAMZ AND Z) OR (JAMN AND N) OR NEXT_ADDRESS[8]
Observe que, en todos los casos, MPC puede adoptar sólo uno de dos posibles valores:
1.       El valor de NEXT_ADDRESS.
2.       El valor de NEXT_ADDRESS después de que se realizó el OR del bit de orden alto con 1.
No existen otras posibilidades. Si el bit de orden alto de NEXT_ADDRESS era 1, no tiene sentido usar JAMN o JAMZ.
Observe que cuando todos los bits de JAM son ceros, la dirección de la siguiente microinstrucción a ejecutar es simplemente el número de nueve bits que está en su campo NEXT_ADDRESS. Si JAMN o JAMZ son 1, hay dos posibles sucesores, NEXT_ADDRESS y el resultado de la operación OR de NEXT_ADDRESS con 0x100 (suponiendo que NEXT_ADDRESS < OxFF). (Cabe señalar que Ox indica que el número que sigue está en hexadecimal.) Este punto se ilustra en la figura 4-7. La microinstrucción en curso, en la posición 0x75, tiene NEXT_ADDRESS = 0x92 y JAMZ = 1. Por tanto, la siguiente direc­ción de la microinstrucción depende del bit Z almacenado en la operación de ALU previa. Si el bit Z es 0, la siguiente microinstrucción provendrá de 0x92. Si el bit Z es 1, la siguiente microinstrucción provendrá de 0x192.
El tercer bit del campo JAM es JMPC. Si está activo, se efectúa la operación OR bit por bit de los ocho bits de MBR con los 8 bits de orden bajo del campo NEXT_ADDRESS de la microinstrucción en curso. El resultado se envía a MPC. El cuadro rotulado “O” en la figura 4-6 efectúa un OR de MBR con NEXT_ADDRESS si JMPC es 1, pero se limita a pasar NEXT_ADDRESS directamente a MPC si JMPC es 0. Cuando JMPC es 1, los ocho bits de orden bajo de NEXT_ADDRESS normalmente son cero. El bit de orden alto puede ser 0 o 1, de modo que el valor de NEXT_ADDRESS que se usa con JMPC normalmente es 0x000 o 0x100. La razón para usar a veces 0x000 y a veces 0x100 se explicará más adelante.

Al mismo tiempo que controla la trayectoria de datos, el microprograma tiene que deter­minar qué microinstrucción ejecutará a continuación, porque no se ejecutan en el orden en que aparecen en el almacén de control. El cálculo de la dirección de la siguiénte microins­trucción se inicia una vez que MIR se ha cargado y estabilizado. Primero se copia el campo NEXT_ADDRESS de 9 bits en MPC. Mientras se está efectuando este copiado, se inspec­ciona el campo JAM. Si tiene el valor 000, no se hace nada más; una vez que concluye el copiado de NEXT_ADDRESS, MPC apunta a la siguiente microinstrucción.
Si uno o más de los bits de JAM son 1, hay que trabajar más. Si JAMN está activado, el flip-flop N de un bit se hace OR con el bit de orden alto de MPC. Del mismo modo, si JAMZ está establecido, el flip-flop Z de un bit realiza la operación OR. Si ambos están establecidos, se realiza la operación OR de ellos. Se necesitan los flip-flops N y Z porque después del flanco ascendente del reloj (mientras el reloj está alto), el B bus ya no está siendo controlado, y no puede suponerse que las salidas de la ALU sigan siendo correctas. Guardar las banderas de estado de la ALU en N y Z hace que los valores correctos se estabilicen y estén disponibles para el cálculo de MPC, sin importar qué esté ocurriendo en tomo de la ALU.
En la figura 4-6 la lógica que efectúa este cálculo se denomina “Bit alto”. La función booleana que calcula es
F= (JAMZ AND Z) OR (JAMN AND N) OR NEXT_ADDRESS[8]
Observe que, en todos los casos, MPC puede adoptar sólo uno de dos posibles valores:
1.       El valor de NEXT_ADDRESS.
2.       El valor de NEXT_ADDRESS después de que se realizó el OR del bit de orden alto con 1.
No existen otras posibilidades. Si el bit de orden alto de NEXT_ADDRESS era 1, no tiene sentido usar JAMN o JAMZ.
Observe que cuando todos los bits de JAM son ceros, la dirección de la siguiente microinstrucción a ejecutar es simplemente el número de nueve bits que está en su campo NEXT_ADDRESS. Si JAMN o JAMZ son 1, hay dos posibles sucesores, NEXT_ADDRESS y el resultado de la operación OR de NEXT_ADDRESS con 0x100 (suponiendo que NEXT_ADDRESS < OxFF). (Cabe señalar que Ox indica que el número que sigue está en hexadecimal.) Este punto se ilustra en la figura 4-7. La microinstrucción en curso, en la posición 0x75, tiene NEXT_ADDRESS = 0x92 y JAMZ = 1. Por tanto, la siguiente direc­ción de la microinstrucción depende del bit Z almacenado en la operación de ALU previa. Si el bit Z es 0, la siguiente microinstrucción provendrá de 0x92. Si el bit Z es 1, la siguiente microinstrucción provendrá de 0x192.
El tercer bit del campo JAM es JMPC. Si está activo, se efectúa la operación OR bit por bit de los ocho bits de MBR con los 8 bits de orden bajo del campo NEXT_ADDRESS de la microinstrucción en curso. El resultado se envía a MPC. El cuadro rotulado “O” en la figura 4-6 efectúa un OR de MBR con NEXT_ADDRESS si JMPC es 1, pero se limita a pasar NEXT_ADDRESS directamente a MPC si JMPC es 0. Cuando JMPC es 1, los ocho bits de orden bajo de NEXT_ADDRESS normalmente son cero. El bit de orden alto puede ser 0 o 1, de modo que el valor de NEXT_ADDRESS que se usa con JMPC normalmente es 0x000 o 0x100. La razón para usar a veces 0x000 y a veces 0x100 se explicará más adelante.
La capacidad para calcular el OR de MBR con NEXT_ADDRESS y almacenar el resul­tado en MPC hace posible una implementación eficiente de una ramificación (salto) de múl­tiples vías. Observe que es posible especificar cualquiera de 256 direcciones, determinada exclusivamente por los bits presentes en MBR. En un uso típico, MBR contiene un código de operación, de modo que el uso de JMPC hará que se seleccione para ejecutarse en seguida una microinstrucción única para cada posible código de operación. Este método es útil para saltar directamente a la función que corresponde al código de operación encontrado.
Para la explicación que sigue es crucial entender la temporización de la máquina, así que tal vez valga la pena repetirla. Lo haremos en términos de subciclos, ya que esto es más fácil de visualizar, los sucesos de reloj reales son el flanco descendente, que inicia el ciclo, y el flanco ascendente, que carga los registros y los flip-flops N y Z. Remítase por favor a la figura 4-3.
Durante el subciclo 1, iniciado por el flanco descendente del reloj, MIR se carga con la dirección que actualmente está en MPC. Durante el subciclo 2, las señales de MIR se propa­gan hacia afuera y el bus B se carga a partir del registro seleccionado. Durante el subciclo 3, la ALU y el desplazador operan y producen un resultado estable. Durante el subciclo 4, el bus C, los buses de memoria y los valores de la ALU se estabilizan. En el flanco ascendente del reloj los registros se cargan a partir del bus C, los flip-flops N y Z se cargan, y MBR y MDR obtienen sus resultados de la operación de memoria que se inició al final del ciclo de trayec­toria de datos anterior (si se inició alguna de esas operaciones). Tan pronto como está dispo­nible MBR, MPC se carga en preparación para la siguiente microinstrucción. Así MPC obtiene su valor en algún momento durante la parte media del intervalo en el que el reloj está alto pero después de que MBR/MDR están listos. Esto podría ser disparado por nivel (en lugar de por flanco) o disparado por flanco con un retraso fijo después del flanco ascendente del reloj. Lo único que importa es que MPC no se cargue sino hasta que los registros de los que depen­de (MBR, N y Z) estén listos. Tan pronto como el reloj baja, MPC puede direccionar el almacén de control y se puede iniciar un ciclo nuevo.
Cabe señalar que cada ciclo es autosuficiente: especifica qué se coloca en el bus B, qué deben hacer la ALU y el desplazador, dónde debe almacenarse el valor del bus C y, por último, qué valor debe tener MPC a continuación.
Vale la pena un último comentario acerca de la figura 4-6. Hemos estado tratando a MPC como un registro normal, con 9 bits de capacidad, que se carga mientras el reloj está alto. En realidad, no hay necesidad de tener un registro ahí. Todas sus entradas se pueden almacenar directamente hasta el almacén de control. En tanto estén presentes en el almacén de control en el flanco descendente del reloj cuando MIR se selecciona y lee, será suficiente. No hay necesidad de almacenarlas realmente en MPC. Por esta razón, MPC bien podría implementarse como un registro virtual, que no es más que un punto de reunión de señales, más parecido a un panel de conexiones electrónico que a un registro real. Hacer que MPC sea un registro virtual simplifica la temporización: ahora sólo ocurren sucesos en los flancos descendentes y ascendentes del reloj, y en ningún otro momento. Pero si para usted es más fácil pensar en MPC como un registro real, también es un punto de vista válido.
4.2 UN EJEMPLO DE ISA: UVM
Continuemos nuestro ejemplo presentando el nivel ISA de la máquina para ser interpretada por el microprograma que se ejecuta en la microarquitectura de la figura 4-6 (IJVM). Por comodidad, algunas veces nos referiremos a la arquitectura del conjunto de instrucciones (ISA) como la macroarquitectura, a fin de contrastarla con la microarquitectura. Sin embar­go, antes de describir IJVM haremos una breve digresión para motivar la descripción.
Prácticamente todos los lenguajes de programación soportan el concepto de procedimientos (métodos) que tienen variables locales. Estas variables pueden accesarse desde el interior del procedimiento pero dejan de ser accesibles una vez que el procedimiento ha regresado. Así, surge la pregunta: “¿en qué lugar de la memoria deben guardarse estas variables?’’
La solución más sencilla, asignar a cada variable una dirección de memoria absoluta, no funciona. El problema es que un procedimiento puede llamarse a sí mismo. Estudiaremos estos procedimientos recursivos en el capítulo 5. Por el momento, basta decir que si un proce­dimiento se activa (es decir, se llama) dos veces, es imposible almacenar sus variables en posiciones de memoria absolutas porque la segunda invocación interferirá la primera.
En vez de ello, se usa una estrategia distinta. Se reserva un área de memoria, llamada pila, para variables, pero las variables individuales no reciben direcciones absolutas dentro de la pi­la. En vez de ello se ajusta un registro, digamos LV (Local Variable) de modo que apunte a la base de las variables locales del procedimiento en curso. En la figura 4-8(a) se ha invocado un procedimiento A, que tiene las variables locales al, a2 y a3, así que se ha reservado memoria para sus variables locales a partir de la posición a la que LV apunta. Otro registro, SP (Stock Pointer, apuntador de la pila), apunta a la palabra más alta de las variables locales de A. Si LV es 100 y las palabras son de 4 bytes, entonces SP será 108. Se hace referencia a las variables es­pecificando a qué distancia están de LV. La estructura de datos entre LV y SP (que incluye las dos palabras a las que se apunta) es el marco de variables locales de A.
Consideremos ahora qué sucede si A llama a otro procedimiento, B. ¿Dónde deberán almacenarse las cuatro variables locales de B (bl, b2, b3, b4f>. Respuesta: en la pila, encima de las de A, como se muestra en la figura 4-8(b). Observe que la llamada de procedimiento