En este capítulo vamos a ver cómo mejorar las prestaciones de la

En este capítulo vamos a ver cómo mejorar las prestaciones de la CPU mediante los
procesadores segmentados (o en pipeline), los cuales incorporan una técnica para acelerar el
ritmo de ejecución de las instrucciones.
En primer lugar empezaremos por comentar algunos aspectos básicos para introducir los
conceptos en los que se apoya esta técnica.
A continuación nos centraremos en nuestro procesador de referencia, el MIPS64, viendo la
configuración de las etapas que componen su cauce.
Como veremos, hay diversos factores que pueden impedir un aprovechamiento óptimo del
concepto de pipeline en la CPU. Trataremos estos factores y las técnicas para evitarlos en la
medida de lo posible.
Por último abordaremos una mejora más del pipeline mediante el concepto de operaciones
multiciclo que reducirán las esperas o paradas generadas por las instrucciones pesadas, como
las multiplicaciones, divisiones o las operaciones en coma flotante.
Arquitectura de Computadores
Segmentación (Pipeline) - 1
La primera opción que se nos ocurre para aumentar la velocidad de un procesador es aumentar la
velocidad del reloj, lo cual es muy fácil, pero claro, si los circuitos que componen las etapas de
ejecución del procesador no soportan esa velocidad, éste se quema o deja de funcionar
correctamente.
Así, para poder aumentar la velocidad del reloj, primero se deben hacer más rápidos los circuitos
con los que se construyen los procesadores y la memoria principal. No obstante, se debe
considerar el coste que supone una mejora, y que el límite de esta velocidad lo impone el estado
del arte actual de la tecnología.
Otra posibilidad es organizar el hardware para poder ejecutar más de una instrucción
simultáneamente: concurrencia. La concurrencia se puede obtener en dos niveles: al nivel del
procesador y al nivel de la instrucción. La concurrencia al nivel de la CPU se obtiene disponiendo
de múltiples procesadores ejecutando simultáneamente varias instrucciones. Obtener
concurrencia a nivel de la instrucción significa poder ejecutar varias instrucciones
simultáneamente con una única CPU. Este último tipo de paralelismo se denomina segmentación,
aunque suele ser más conocido por su denominación en inglés: pipelining.
Las arquitecturas con múltiples procesadores suelen utilizarse en máquinas de muy altas
prestaciones (y muy alto precio). Sin embargo, con arquitecturas segmentadas se consigue una
muy buena mejora del rendimiento y a un coste asequible. Por esto, es normal que todos los
microprocesadores actuales de propósito general incorporen el pipelining. Ya que es muy común
su utilización en los actuales procesadores, vamos a abordar aquí esta técnica del pipelining,
mientras que las arquitecturas multiprocesador las dejaremos para asignaturas o textos de
arquitecturas paralelas o avanzadas.
Arquitectura de Computadores
Segmentación (Pipeline) - 2
Ahora ya podemos abordar el concepto de pipeline. El proceso en pipeline (o segmentado) es
similar al utilizado en cualquier cadena de montaje, y el nombre pipeline (tubería) se debe al
hecho de que, como en una tubería, en la entrada se aceptan nuevos elementos (instrucciones)
antes de que los previamente aceptados salgan por la salida.
Empecemos con el ejemplo de una cadena de montaje. Supongamos una gran pastelería en la
que las tartas primero se hacen en el horno y después se empaquetan para la venta. El proceso
de empaquetar una tarta consiste en:
1. Poner una caja vacía en la mesa.
2. Meter una tarta en la caja.
3. Cerrar y precintar la caja.
4. Poner una etiqueta en la caja.
5. Llevar la caja a un gran contenedor.
Si cada una de estas operaciones la realiza un operario en 10 segundos, parece claro que se
tarda 50 s en empaquetar una tarta y, por lo tanto, en empaquetar 10 tartas se tardaría 500 s.
Arquitectura de Computadores
Segmentación (Pipeline) - 3
Ahora supongamos que se dispone de una cadena de empaquetado de tartas con una cinta
transportadora sobre la que trabajan cinco operarios especializados en tareas distintas. El primer
operario pone la caja-1 en la cinta transportadora, y ésta avanza hasta que la caja-1 está donde el
segundo operario, que introduce una tarta dentro de la caja-1, al mismo tiempo que el primer
operario pone otra caja-2 en la cinta. La caja-1 sigue avanzando hasta el tercer operario, que la
cierra y la precinta, al mismo tiempo que el segundo operario mete otra tarta en la caja-2 y el
primer operario pone otra caja-3 en la cinta. La caja-1 sigue su camino en la cinta pasando por el
cuarto operario, que pone una etiqueta, hasta llegar al quinto operario, que la retira de la cinta.
En el momento que el quinto operario retira la caja de la cinta, hay cuatro cajas más en la cinta. Si
cada una de estas fases de empaquetado se realiza en 10 s, a partir de ahora, cada 10 s saldrá
una nueva tarta empaquetada, en lugar de hacerlo cada 50 s que se tardaba cuando no había
cadena de empaquetado. A partir de ahora, solamente se tardará100 segundos en tener 10 tartas
empaquetadas, mientras que en el caso de cuando se tenía un solo operario se tardaba 500
segundos.
Debe quedar claro que aunque ahora sale una nueva tarta empaquetada cada 10 s, la
preparación completa de cada tarta sigue requiriendo 50 s (igual que cuando había una sola
persona preparando las tartas).
Ha aumentado el rendimiento, pero se mantiene el tiempo de empaquetado de cada tarta.
Si calculamos el rendimiento de los dos sistemas de empaquetado de tartas, veremos que el
rendimiento en este último caso se ha multiplicado por 5 (¡igual a número de etapas!).
Arquitectura de Computadores
Segmentación (Pipeline) - 4
Según lo que acabamos de ver, parece que interesa dividir las fases de ejecución de las
instrucciones en más etapas, para así obtener un mayor rendimiento en la ejecución.
La ejecución de una instrucción podría descomponerse en las siguientes 5 etapas:
1.
2.
3.
4.
5.
F:
D:
E:
M:
W:
Alimentación de la instrucción (fetch)
Decodificación de la instrucción / Lectura de registros
Ejecución (en la ALU) / Cálculo de la dirección efectiva
Acceso a memoria
Escritura del resultado en registros de la CPU
Si ahora la ejecución de una instrucción está descompuesta en 5 etapas, cada etapa puede durar
aproximadamente 1/5 de la duración total de la ejecución de la instrucción. Si suponemos que la
duración de un ciclo de reloj es igual a la duración de cada una de estas pequeñas etapas,
podemos decir, en principio, que con la técnica de la segmentación (o pipelining) se consigue que
a cada ciclo de reloj finalice una instrucción, o lo que es lo mismo, una velocidad de instrucción
por ciclo.
Debemos tener en cuenta que:
• Cada etapa dispone de los recursos hardware necesarios para realizar su cometido.
• Las ejecuciones de las instrucciones se solapan.
• Todas las etapas tienen la misma duración (ciclo de reloj).
• La duración del ciclo de reloj lo fija la etapa más lenta.
Arquitectura de Computadores
MIPS64 - 5
En este histograma podemos ver la comparación entre dos procesadores de similares
características y que su gran diferencia consiste en estar o no estar segmentados. Utilizaremos el
procesador del ejemplo que hemos visto en la página anterior, en el que supondremos que el ciclo
de reloj es 1 ns.
El ciclo de reloj es igual para ambos casos (1 ns), ya que partimos de dos procesadores con
similares características.
El tiempo de ejecución de la instrucción (si todas las instrucciones pasan por todas las etapas)
también es igual en ambos casos (5 ciclos).
En cuanto al rendimiento, el procesador no segmentado ejecuta una instrucción, que tarda 5
ciclos, y hasta que no termina, no comienza la ejecución de otra instrucción, por lo que termina
una instrucción cada 5 ciclos, o sea, que se consigue un rendimiento de 1 instrucción cada 5
ciclos. En cambio, en el caso del procesador segmentado, cuando una instrucción arranca, al ciclo
siguiente se arranca la siguiente instrucción y, de esta manera, cuando la primera instrucción
termina, al ciclo siguiente termina la segunda. De esta manera, tenemos que a cada ciclo finaliza
una instrucción, consiguiente un rendimiento de una instrucción por ciclo o, como vemos en el
gráfico, de 5 instrucciones cada 5 ciclos.
Arquitectura de Computadores
Segmentación del Cauce - 6
Como habíamos visto anteriormente, la aceleración o speed up, es el cociente entre el tiempo
medio que transcurre entre la finalización de las instrucciones en el procesador no segmentado y
en el segmentado.
En el procesador segmentado, en condiciones ideales, a cada ciclo finaliza la ejecución de una
instrucción, consiguiendo un número medio de ciclos por instrucción (CPI) igual a 1.
No debemos olvidar que en un procesador segmentado (una vez que está lleno el cauce y en
condiciones ideales), en un momento dado hay tantas instrucciones en ejecución como etapas
tiene; no obstante, cada etapa se está ocupando de una función distinta de cada instrucción.
Arquitectura de Computadores
Segmentación del Cauce - 7
Veamos la mejora de aceleración que se experimenta al segmentar un procesador convencional,
cuando ejecuta un programa que consta del 40% de instrucciones aritméticas, un 20% de
instrucciones de salto y otro 40% de instrucciones de acceso a operandos en memoria principal.
Las instrucciones aritméticas del procesador no segmentado requieren 4 ciclos, mientras que las
de salto y acceso a memoria consumen 5 ciclos de reloj.
El procesador segmentado consta de 5 etapas, y tiene un retardo de 0,2 ns para el control y el
paso de datos a cada etapa posterior. El ciclo de reloj en ambos casos es de 1 ns.
El tiempo medio de ejecución de una instrucción en el procesador NO segmentado se obtiene
mediante la suma de los tiempos de ejecución de cada tipo de instrucción por sus frecuencias de
aparición en los programas, obteniendo así, un valor medio de 4,6 ns.
En el caso del procesador segmentado, tenemos que cada etapa requiere 1 ciclo de reloj (1 ns).
Así tenemos que cada instrucción tardará en ejecutarse un total de 5 ns, pero, en realidad, cada 1
ns finalizará una instrucción.
De esta manera, la mejora en rendimiento (speed up) que se experimenta es el cociente entre el
tiempo medio que transcurre entre la finalización de las instrucciones en el procesador no
segmentado y el segmentado, consiguiendo así, una mejora del 4,6. Esto significa que, para
programas con esta distribución de frecuencias en sus instrucciones, el procesador segmentado
es 4,6 veces más rápido que el convencional.
Arquitectura de Computadores
MIPS64 - 8
Arquitectura de Computadores
MIPS64 - 9
Una vez vistos los conceptos básicos del pipeline, vamos a concretarlo en una versión simplificada
de la arquitectura MIPS64.
En un principio vamos a considerar una versión básica de 5 etapas, en la que la Unidad
Aritmético-Lógica solamente va a operar con datos enteros.
El tiempo de ejecución de cada etapa va a ser un ciclo.
En esta versión básica (solo para un grupo reducido de instrucciones) vamos a disponer de una
estructura en la que veremos que las instrucciones de salto generan ciertos retardos. Más
adelante mejoraremos la estructura para conseguir mejorar el problemas de los retardos.
Ahora iremos mostrando en las siguientes páginas la descripción de cada una de las 5 etapas de
este pipeline simplificado de MIPS64.
Arquitectura de Computadores
MIPS64 - 10
En esta primera etapa se extrae o alimenta una instrucción de memoria, de la dirección indicada
en el registro Contador de Programa (PC), y se almacena en el registro de instrucción IR
(Instruction Register).
A continuación se calcula la dirección de la siguiente instrucción en secuencia (por dirección en
memoria), añadiéndole al PC el tamaño de la instrucción alimentada. En nuestro caso, todas las
instrucciones son de longitud constante, 4 bytes. La dirección calculada se guarda en el registro
NPC (Next Program Counter).
La instrucción queda en el registro IR de donde se irá extrayendo información en las sucesivas
etapas.
Arquitectura de Computadores
Segmentación del Cauce - 11
Aquí se realizan 3 funciones:
•  Decodificación de la instrucción alimentada, es decir, se averigua cuál es la operación que se
debe realizar.
•  En paralelo se lee el contenido de los registros indicados como operandos en la instrucción, y
se guardan en los registros temporales A y B.
•  Si la instrucción contiene un campo de 16 bits con un valor inmediato, se le hace una extensión
de signo a 64 bits y se guarda en el registro Inm.
Es posible que, según la operación a realizar, tanto el contenido de los registros leídos como el
valor inmediato extraído, tenga o no sentido. Si la operación no requiere alguno de los registros
leídos o el valor inmediato, simplemente no se utiliza. En cualquier caso, no se pierde tiempo al
hacerlo, ya que la lectura se realiza, en paralelo, al mismo tiempo que la decodificación.
Arquitectura de Computadores
MIPS64 - 12
Esta es la etapa de la Unidad Aritmético-Lógica (ALU), donde se realiza una de estas cuatro
posibles funciones, dependiendo del tipo de instrucción:
•  Instrucción con registros (A op B à ALUoutput). La ALU realiza la operación indicada por el
código de función u operación, utilizando los valores de los registros temporales A y B. El
resultado se deja en el registro temporal ALUoutput.
•  Instrucción registro-Inmediato (A op Inm à ALUoutput). La ALU realiza la operación
indicada por el código de función u operación, utilizando los valores del registro temporal A y el
valor del registro Inm (que contiene el valor inmediato de la instrucción). El resultado se deja en
el registro temporal ALUoutput.
•  Referencia a memoria (A+Inm à ALUoutput). La ALU suma estos operandos para formar
una dirección absoluta de memoria. El resultado se deja en el registro ALUoutput.
•  Bifurcación. En este tipo de instrucciones, la ALU hace dos cosas: Primero añade al NPC el
valor inmediato Inm desplazado 2 bits a la izquierda (o sea, multiplicado por 4) para crear un
desplazamiento y calcular la dirección de destino el salto, dejando el resultado en ALUoutput.
A continuación se comprueba si el registro A (cuyo valor se ha establecido en la etapa D) tiene
el valor 0, y el resultado de la comparación se establece en el registro Cond.
Estas cuatro funciones distintas pueden estar en la misma etapa (aunque solo se ejecuta una de
ellas en cada instrucción) ya que nunca se requiere que se haga más de una de estas
operaciones sobre la misma instrucción.
Los desplazamientos de los saltos se indican como múltiplos de 4 pues como todas las
instrucciones ocupan 4 bytes, siempre están en una dirección múltiplo de 4.
Arquitectura de Computadores
MIPS64 - 13
Para todo tipo de instrucciones, en esta etapa se actualiza el PC con el valor del NPC. Además,
dependiendo del tipo de instrucción, se pueden realizar dos operaciones:
•  Referencia a memoria. Dependiendo de si se trata de una carga o un almacenamiento, se
realiza una acción. Si es una carga, el dato traído de memoria se deja en el registro temporal
LMD; si se trata de un almacenamiento, el dato del registro temporal B se escribe en memoria.
En cualquier caso, la dirección de memoria utilizada es la calculada en el ciclo (etapa) anterior y
que se dejó en ALUoutput.
•  Bifurcación. Si la instrucción es una bifurcación y se cumple la condición del salto, el valor que
se lleva al PC es la dirección almacenada en ALUoutput, en lugar del valor contenido en el
NPC.
Arquitectura de Computadores
MIPS64 - 14
En esta etapa se escribe un valor en alguno de los registros generales (si la instrucción lo
requiere), bien si el valor viene de una posición de memoria (valor en registro temporal LMD) o se
trata del resultado de una operación en la ALU.
El registro en el que se deja el resultado de la operación con la ALU, depende del tipo concreto de
instrucción ejecutada (tipo R o tipo I).
Arquitectura de Computadores
MIPS64 - 15
A modo de resumen, aquí tenemos un esquema general de las etapas de MIPS64
1.  F (Alimentación de instrucción – Instruction Fetch). Se extrae una instrucción de la dirección de
memoria indicada por el Contador de Programa (PC) y se incrementa éste en 4, para que
apunte a la dirección de la siguiente instrucción.
2.  D (Decodificación de Instrucción / Lectura de registros). Se decodifica la instrucción extraída y
se lee el contenido de los registros indicados como operandos en la instrucción. Si es una
instrucción de salto, se calcula la dirección de destino incrementando el PC con el
desplazamiento indicado en la instrucción. En cualquier caso, la decodificación se realiza en
paralelo con la lectura de los registros.
3.  E (Ejecución/Cálculo de la dirección de la dirección efectiva). Aquí, dependiendo del tipo de
instrucción, se realiza una de estas funciones:
•  Cálculo de dirección efectiva (registro base + desplazamiento), si es un salto o carga/
almacenamiento.
•  Ejecución de una operación en la ALU (Unidad Aritmético-Lógica), si es una instrucción
aritmética, lógica, desplazamiento…
4.  M (Acceso a memoria). Si la instrucción es de acceso a memoria, para lectura o escritura
(carga/almacenamiento), se realiza en esta fase, utilizando para ello el valor del registro leído
en la fase D (si es una escritura en memoria) y la dirección de memoria calculada en la fase E.
5.  W (Escritura en registros). Si el resultado de la instrucción, o lectura de memoria, tiene como
destino un registro, se escribe en éste en esta fase.
Arquitectura de Computadores
Segmentación del Cauce - 16
En esta figura se muestra una primera aproximación para conseguir la arquitectura de MIPS que
se ha comentado en las páginas anteriores, y se puede ver cómo fluye una instrucción a través del
cauce.
No obstante, esta aproximación provisional no se corresponde con el cometido de las etapas
visto en la página anterior, cuya arquitectura exacta la abordaremos más adelante realizando
las mejoras oportunas. Éste es un pipeline simplificado para la ejecución de estas operaciones:
−  Load/Store
−  Operaciones con enteros
−  Salto condicional si “igual a cero”
Como puede verse, al final de cada etapa, los valores obtenidos en ella, deben guardarse para
poder acceder a ellos desde una etapa posterior. Por ello, se salvan en unos registros temporales
(IR, A, B, Inm, NPC, ALUoutput, LMD, Cond).
Se puede apreciar que algunos registros están ubicados en las etapas en las que se lee su
contenido, pero su escritura o actualización se produce en otras etapas. Así, el registro PC está
situado en la etapa de extracción de la instrucción (F), pero, en realidad, se escribe desde la etapa
M. Los registros generales están ubicados en la etapa D, pero se escriben durante la etapa WB.
Estas señales de flujo hacia atrás le generan mucha complejidad al cauce, ya que, como pronto
veremos, son los responsables de los “riesgos” o peligros del pipeline.
Arquitectura de Computadores
Segmentación del Cauce - 17
En la figura de arriba se muestran algunas de las características del pipeline básico de la
arquitectura MIPS64.
Arquitectura de Computadores
Segmentación del Cauce - 18
Arquitectura de Computadores
MIPS64 - 19
Una vez elegido el número óptimo de etapas, para que el factor de aceleración sea igual al
número de etapas se requiere que todas las etapas del pipeline siempre estén llenas de
instrucciones útiles, y que nada retrase el avance de las instrucciones a través del pipeline.
Por desgracia, no es fácil mantener siempre ocupadas todas las etapas del pipeline. Hay tres
causas que lo impiden:
•  Motivos estructurales.
•  Dependencias de operandos.
•  Instrucciones de bifurcación.
En las siguientes transparencias las comentaremos con cierto detalle.
Arquitectura de Computadores
Segmentación (Pipeline) - 20
Como ya veremos, se tiende a que la ejecución de cada etapa se realice en un ciclo de reloj. Pues
bien, cuando una etapa no es capaz de realizar su cometido en un ciclo de reloj, el pipeline
se detiene hasta que dicha etapa finaliza su trabajo. Hay varias causas estructurales (arquitectura
del pipeline) que pueden hacer que el pipeline se detenga.
Por ejemplo, puede ocurrir que no todas las etapas sean de la misma duración, con lo que
alguna etapa de corta duración debería esperar a que acabe la siguiente que es más larga. Esto
hará que la duración efectiva de cada etapa sea igual a la duración de la etapa más larga.
Normalmente los procesadores actuales tienden a un alto número de etapas, con lo que
automáticamente tienden a igualarse los tiempos.
Otra cosa que también puede ocurrir es que desde varias etapas se quiera acceder a memoria
simultáneamente (por ejemplo en la etapa de alimentación de instrucción y en la escritura del
resultado). Y, claro, si una etapa se detiene para esperar a poder realizar el acceso a memoria, el
pipeline se para.
También tenemos que considerar que no todas las instrucciones hacen las mismas cosas, por
lo que requieren tiempos distintos de CPU. Pasemos a la siguiente página para tratar este caso
con más detalle.
Arquitectura de Computadores
Segmentación (Pipeline) - 21
No todas las instrucciones hacen las mismas cosas y requieren el mismo tiempo de CPU.
Unas pueden necesitar más tiempo en la etapa de ejecución (por ejemplo, la carga o escritura de
un registro requiere menos trabajo de ALU que una división en coma flotante), mientras que otras
pueden necesitar más tiempo para obtener los operandos o escribir el resultado (si están en
memoria principal se tarda más que si están en registros).
En el ejemplo de la transparencia vemos que la instrucción I2 no puede completar la fase de
ejecución en el ciclo 4, necesitando para ello también los ciclos 5 y 6. Esto hace que en el 5º ciclo
no pueda alimentarse la instrucción I5 por estar ocupada la etapa de extracción de instrucción,
debiendo esperar ésta al ciclo 7 para poder continuar extrayendo instrucciones. Obsérvese que
como consecuencia del sobretiempo de E2, al término de los ciclos 6 y 7 no finaliza ninguna
instrucción (lo cual va en perjuicio del rendimiento).
Puede suceder incluso que alguna de las etapas ni siquiera necesite ejecutarse. Por ejemplo,
en un procesador cuya última etapa se dedique a escribir en memoria principal, la carga de un
registro no requerirá dicha última etapa. En este caso, simplemente sucederá que cuando una
instrucción corta va después de una normal, ambas finalicen su ejecución simultáneamente, y en
el siguiente ciclo de reloj no terminará ninguna instrucción; por lo tanto, el rendimiento no varía.
Arquitectura de Computadores
Segmentación (Pipeline) - 22
Si desde dos etapas se accede a un mismo recurso, una de las etapas tendrá que detener su
ejecución y esperar a que quede libre el recurso necesario.
Por ejemplo, en las etapas de Fectch y Ejecución puede ser necesario un acceso simultáneo a la
ALU para realizar una operación aritmética, como el incremento que debe hacer al PC para
actualizarlo (en F) o la suma que se requiere para realizar el cálculo de la dirección efectiva de un
operando (en E). Esto se soluciona fácilmente mediante dos sumadores distintos.
Por otra parte, también se puede requerir un acceso simultáneo a memoria desde las etapas F y
M; para extraer la siguiente instrucción a ejecutar y para leer o escribir un operando o resultado en
memoria.
Arquitectura de Computadores
Segmentación del Cauce - 23
Para mejorar el acceso simultáneo a algunos recursos, como los registros, el ciclo se divide en
dos subciclos, de tal manera que dos etapas pueden coincidir en el recurso en el mismo ciclo. Por
ejemplo, la etapa W podría escribir en un registro en el primer subciclo, y otra instrucción podría
leer su contenido en el segundo subciclo, desde la etapa D (lectura de registros).
Arquitectura de Computadores
Segmentación del Cauce - 24
Las dependencias de datos se producen cuando dos instrucciones comparten un dato (operando
o resultado).
Hay tres tipos de dependencias de datos:
•  Verdadera dependencia de datos à RAW (Read After Write)
•  Antidependencia
•  Dependencia de salida
à WAR (Write After Read)
à WAW (Write After Write)
Los distintos tipos de dependencias pueden dan lugar a otros tantos tipos de “riesgos de
datos” (RAW, WAR y WAW).
Realmente, la única dependencia verdadera de datos es la que da lugar a riesgo de tipo RAW, que
viene dada por la propia lógica del programa; los otros dos tipos son “dependencias de nombre” y
dependen de las características del cauce.
Más adelante trataremos las dependencias de nombre, pero ahora vamos a ocuparnos de la
verdadera dependencia de datos (RAW).
La situación es la siguiente: Una instrucción Ij actualiza el valor de una variable, pero una
instrucción posterior, Ik, realiza una lectura de esa variable antes de que Ij haya terminado la
operación escribiendo la variable compartida.
Veamos, en las siguientes páginas ejemplos concretos de esta dependencia de datos y varias
soluciones propuestas.
Arquitectura de Computadores
Segmentación (Pipeline) - 25
En el programa del ejemplo, la dependencia que se denomina “lectura después de
escritura” (Read After Write, o RAW) puede producirse entre las instrucciones I2 e I3 si la
instrucción dmul lee el contenido de R1 (en el segundo subciclo de t4) antes de que el resultado
de la suma anterior (en el primer subciclo de t6) se cargue en él. Obviamente, la operación dmul
no se ejecutará con los operandos esperados por el programador, por lo que el resultado del
programa será incorrecto.
Hay dos opciones básicas para resolver este problema de dependencia de datos; uno es mediante
la prevención: evitando que pueda llegarse a esta situación de dependencia; el otro es mediante
la detección y resolución, es decir, no preocupándose de evitarlo, pero sí de detectarlo en caso
de que se produzca y solucionarlo de alguna manera. Veámoslas en detalle.
Arquitectura de Computadores
Segmentación (Pipeline) - 26
La dependencia de datos: Prevención.
El problema de la dependencia de datos entre una instrucción I1 y otra instrucción I2 que le sigue
puede prevenirse retrasando la ejecución de I2 un número K de etapas hasta que desaparezca el
problema de que I2 lea un operando que I1 no ha escrito todavía. Este retraso puede conseguirse
insertando un número K de instrucciones entre I1 e I2. Esto significa que el compilador tiene que
reordenar el programa para encontrar K instrucciones que puedan ejecutarse después de I1 y
antes de I2 sin que por ello varíe la estructura lógica del programa.
Ejemplo 1: En la figura tenemos el ejemplo de un programa en el que hay una dependencia
entre las instrucciones I2 e I3 a causa del registro R1. Como vemos, en este programa el
compilador puede detectar la dependencia de datos y reorganizar las instrucciones para
retardar el acceso al registro R1 hasta que esté actualizado. Debe quedar claro que esta
reorganización solamente puede hacerse si se mantiene la semántica original del programa.
Por lo que hemos visto en el ejemplo de la página anterior, para evitar la dependencia de I3
respecto a I2, se requiere que I3 comience su ejecución tres ciclos después de que lo haga I2.
(Suponemos que en el primer subciclo de t6, se escribe el resultado de daddi en R1, y en el
segundo subciclo, se lee el operando R1 de dmul).
Como se puede apreciar, esto se ha conseguido con la reorganización que ha realizado el
compilador, intercambiando el orden I2 por I1 e I3 por I4.
Arquitectura de Computadores
Segmentación (Pipeline) - 27
Si el compilador no puede reorganizar el código para encontrar estas K instrucciones que
decíamos arriba, sin modificar la lógica del programa, debe insertar operaciones NOP (No
Operación) entre las operaciones dependientes.
Ejemplo 2: En el ejemplo inferior tenemos el fragmento de un programa en el que también hay
dependencias entre las instrucciones I1, I2 e I3. En este caso vemos que las instrucciones de
este fragmento no se pueden reordenar sin alterar la lógica del programa, por lo que el
compilador inserta las instrucciones NOP necesarias para evitar la ejecución errónea de
instrucciones por las dependencias de datos.
La ventaja de la solución basada en la prevención es que no se requiere hardware adicional, pero
a expensas de un compilador más complejo y una pérdida de tiempo si es necesario insertar
instrucciones NOP (cuando no se puede reordenar el programa para insertar instrucciones útiles).
Arquitectura de Computadores
Segmentación (Pipeline) - 28
La dependencia de datos: Detección y resolución.
Este método requiere un hardware adicional en la CPU, pues se deben detectar las dependencias
de datos durante la ejecución y resolver estas dependencias. Detectarlas significa que debe darse
cuenta de que en un momento dado hay dos instrucciones arrancadas I1 e I2, tal que I2 depende
de un resultado establecido por I1. El dispositivo hardware que detecta estas dependencias se
denomina interlock. Resolver las dependencias significa hacer algo para retrasar la ejecución de I2
o para acelerar, en la medida de lo posible, la entrega a I2 del resultado que produce I1.
Veamos dos posibilidades de resolución (no excluyentes) para cuando se detecta una
dependencia de datos entre dos etapas del pipeline.
• Detener el pipeline. La aproximación más sencilla para evitar los problemas de dependencias
de datos con ayuda del hardware es detener la actividad en las etapas necesarias del pipeline
hasta que desaparezca la dependencia, es decir, hasta que se pueda ejecutar correctamente la
instrucción dependiente. En el ejemplo de la figura, esto significa detener las instrucciones que
siguen a la instrucción daddi desde el ciclo 4 hasta que el registro R1 pueda leerse
debidamente actualizado en segundo subciclo de t6.
Con esta estrategia, primero se detecta la dependencia y después se detiene el pipeline a partir
de la instrucción dependiente hasta que desaparece la dependencia.
Obsérvese que la detención de n ciclos del cauce tiene el mismo efecto que la inserción de n
instrucciones NOP.
Arquitectura de Computadores
Segmentación (Pipeline) - 29
El otro modo de resolver las dependencias de datos mediante Detección y Resolución es
acelerando, en la medida de lo posible, la entrega a una instrucción I2 del resultado que produce
una instrucción previa I1. Veámoslo en detalle:
Anticipación (data forwarding). En una dependencia de datos RAW, puede suceder que una
instrucción I2 necesite un operando en la etapa D (decodificación y obtención de operandos) que
debe producirlo en el mismo ciclo la instrucción I1 en su etapa E (ejecución). Esto obligaría a
detener la instrucción I2 hasta que I1 escriba el resultado en su etapa W, y entonces pueda
continuar I2 en su etapa D y leer el resultado que produjo I1.
Este retraso puede evitarse redirigiendo (forwarding) el resultado de la etapa E de la instrucción I1
directamente a la entrada de la etapa E de la instrucción I2, obteniendo el mismo efecto que se
obtendría en la ejecución de la etapa D de I2.
En la figura se muestra el esquema de la ejecución de 3 instrucciones, donde la I2 tiene una
dependencia de datos de I1 (por R1), e I3 la tiene a su vez de I2 (por R2).
Para evitar la lectura incorrecta de datos o las paradas del cauce, se puede dirigir el resultado de
la etapa E de I1 directamente a la entrada de la etapa E de I2. Igualmente, también se puede dirigir
el resultado de la etapa E de I2 directamente a la entrada de la etapa E de I3.
En este caso se consigue evitar totalmente la detención del pipeline, pero hay otras situaciones
en las que esto no es posible.
Arquitectura de Computadores
Segmentación (Pipeline) - 30
Aquí tenemos otro caso de dependencias múltiples que se intentan resolver con anticipación,
pero en la situación de la instrucción de carga I1, su resultado (R1), no se consigue hasta el final
de la etapa M (la de acceso a memoria), por lo que hay que detener I2 en su etapa D para dar
tiempo a que I1 ejecute su etapa M y, en el ciclo siguiente, la instrucción de suma pueda obtener el
valor actualizado de R1 a la entrada de su etapa de ejecución .
Como vemos, en las instrucciones de carga no se consigue evitar por completo la parada del
cauce (se requiere una parada de un ciclo), pero si no hubiera adelantamiento, se necesitaría una
parada de 2 ciclos adicionales ciclos en la etapa F de la suma, pues habría que esperar a que se
completara la etapa W de la instrucción de carga para dar paso a la etapa D (obtención de
operandos) de la suma.
Arquitectura de Computadores
Segmentación (Pipeline) - 31
Como se ha mostrado en las páginas anteriores, hay más de una secuencia de instrucciones que
conduce a distintas situaciones de adelantamientos.
Aquí mostramos algunas situaciones de adelantamientos en MIPS, en cuyo caso, los
adelantamientos siempre se producen desde el final de la etapa de ejecución E (casos 1, 2 y 3) o
desde el final de una lectura en memoria (casos 4, 5 y 6) para cargar un registro.
El adelantamiento se produce hacia la etapa de la instrucción en la que se requiere el dato de
entrada.
En los casos 3 y 5, el adelantamiento se produce hacia la etapa D, ya que es la encargada de
comprobar la condición de salto (considerando la arquitectura mejorada que veremos en breve).
En el caso 5, lo cierto es que no se consigue ninguna ventaja con un adelantamiento, pues en el
mismo ciclo en el que la etapa M de la instrucción de carga puede adelantarle R1 a la instrucción
de bifurcación en su etapa D, se ejecuta la etapa W de la instrucción de carga, escribiendo R1 en
el primer subciclo, y la instrucción de bifurcación puede leerlo en el segundo subciclo de su etapa
D.
Arquitectura de Computadores
Segmentación del Cauce - 32
Ya hemos comentado que uno de los principales problemas en el diseño de un pipeline consiste
en asegurar el mantenimiento de un flujo constante de instrucciones alimentando sus diversas
etapas para así poder mantener también constante el ritmo de ejecución de instrucciones
(idealmente, una por ciclo).
El flujo normal de ejecución de un programa es secuencial, por lo que las instrucciones que se van
alimentando y ejecutando están en direcciones consecutivas de memoria. Por desgracia, las
instrucciones de bifurcación (que suelen representar alrededor del 20% de las instrucciones
ejecutadas) pueden romper el flujo constante de instrucciones alimentadas.
Cuando se alimenta una instrucción en la CPU, lo primero que se suele hacer en la etapa Fetch es
incrementar el registro Contador de Programa (PC) para conocer la dirección de la siguiente
instrucción a ejecutar y extraerla en el siguiente ciclo de reloj. Pero si se trata de una instrucción
de salto condicional, hay que esperar a una etapa posterior para que se pueda saber si se cumple
o no la condición de salto, por lo que la etapa de alimentación de instrucción no sabe de dónde
seguir alimentando instrucciones.
¡Tenemos un problema con los saltos!
Arquitectura de Computadores
Segmentación (Pipeline) - 33
Una instrucción de bifurcación condicional hace que la dirección de la siguiente instrucción a
ejecutar no se conozca en la etapa F, por lo que esta etapa de alimentación no puede extraer la
siguiente instrucción a una bifurcación hasta que ésta llegue a la etapa en la que ya se conoce la
dirección de la siguiente instrucción a ejecutar. (En la ruta de datos que hemos visto
anteriormente, esto sucede en la etapa M). Por esto, a continuación de la alimentación de la
instrucción de bifurcación, las etapas F, D… se van quedando vacías al no saber qué instrucción
alimentar.
A estas etapas vacías que aparecen se las denomina huecos de retardo (delay slots).
En algunos sistemas, las bifurcaciones incondicionales pueden detectarse en la fase de
alimentación o extracción (fetch) si se le añade un poco hardware a esta primera etapa. Este
hardware de ayuda también extrae la dirección de salto, con lo que se puede proseguir la
extracción de instrucciones de la dirección del salto.
Sin embargo, en el caso de las bifurcaciones condicionales no se puede hacer esto, pues
puede ocurrir que la condición del salto se establezca precisamente en la instrucción anterior, con
lo que no hay más remedio que esperar a que la bifurcación llegue a la etapa de comprobación de
la condición de salto y establezca la dirección de la siguiente instrucción a ejecutar. Esto quiere
decir que se debe detener la alimentación de instrucciones al pipeline hasta que en la etapa que
corresponda se averigüe la dirección de la siguiente instrucción a ejecutar.
Afortunadamente, hay diversas técnicas que pueden evitar o minimizar el impacto de las
instrucciones de bifurcación, tales como la “bifurcación retardada”, la “predicción del salto” y
algunas más. Veamos estas dos primeras técnicas.
Arquitectura de Computadores
Segmentación del Cauce - 34
Una mejora del cauce de MIPS: Ahora la dirección del salto se calcula en la etapa D, por lo que,
ante un salto, solamente se retarda un ciclo la alimentación de la siguiente instrucción (lo óptimo
sería poder alimentarla en la etapa F).
Obsérvese que cuando la instrucción de salto está en la etapa D, sería deseable que en la etapa F
se pudiera alimentar ya la siguiente instrucción. Con este cauce, la siguiente instrucción se
alimenta un ciclo después de la etapa D, por lo que solamente se pierde un ciclo en los saltos.
Arquitectura de Computadores
Segmentación del Cauce - 35
Con la mejora comentada, ahora la dirección de salto se establece en la etapa D, con lo que
solamente hay que esperar un ciclo más (que en una instrucción distinta de salto) para conocer la
dirección de la siguiente instrucción, por lo que solo se produce un hueco de retardo.
Arquitectura de Computadores
Segmentación del Cauce - 36
El problema de los saltos: Bifurcación retardada.
Ya hemos visto que cuando entra una instrucción de salto en el pipeline, se producen h huecos
de retardo por lo que hay que esperar h ciclos hasta que llega la siguiente instrucción de la
secuencia a ejecutar. Estaría bien que estos huecos se pudieran rellenar con instrucciones que
siempre se deban ejecutar, independientemente de si se toma la bifurcación o no. Pero claro, si
estas instrucciones están en memoria inmediatamente después de la instrucción de bifurcación,
según lo que sabemos hasta ahora, no se ejecutarán si la instrucción de bifurcación decide saltar.
Para conseguir que se ejecuten siempre las h instrucciones que siguen a una bifurcación, en los
procesadores que tienen bifurcaciones retardadas, las instrucciones de salto no tienen efecto
hasta h instrucciones después de su ejecución, por lo que independientemente del resultado de la
ejecución de la bifurcación, siempre se ejecutan las h instrucciones siguientes. De esta manera no
se producen los huecos de retardo.
Arquitectura de Computadores
Segmentación (Pipeline) - 37
Supongamos la serie de instrucciones de la transparencia, en la que I6 es la instrucción de salto
condicional a la instrucción I11. En una CPU sin saltos retardados, la secuencia de instrucciones
ejecutadas cuando la bifurcación no tiene lugar es: I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, ... Mientras
que cuando sí se produce la bifurcación, la secuencia es: I1, I2, I3, I4, I5, I6, I11, I12, ...
Si a este mismo procesador (con un hueco de retardo en las bifurcaciones) se le ponen
bifurcaciones retardadas, la secuencia de instrucciones ejecutadas cuando se produce la
bifurcación es: I1, I2, I3, I4, I5, I6, I7, I11, I12, ... Es decir I7, se ejecuta siempre, haya o no bifurcación.
Esto es así porque el efecto de la bifurcación se retarda un ciclo de reloj, es decir, después de
alimentar una instrucción de salto, se siguen extrayendo y ejecutando las instrucciones siguientes,
según su orden en la memoria, durante un ciclo más de reloj. Después, se establece en el
contador de programa la dirección indicada en la instrucción de salto, con lo que la 2ª instrucción
después de la de salto, será ya la correspondiente a la de la dirección indicada en dicha
instrucción de salto.
Arquitectura de Computadores
Segmentación (Pipeline) - 38
Aquí tenemos un ejemplo, en un ensamblador hipotético, en el que para un programa dado (el de
la izquierda), se muestran en la parte derecha las dos posibilidades de ejecución en un
procesador con un hueco de retardo en los saltos: tanto cuando no se toma la bifurcación, como
cuando sí se toma.
Arquitectura de Computadores
MIPS64 - 39
A partir de un fragmento de código escrito para un procesador sin saltos retardados, si se va a ejecutar en
una CPU con saltos con un hueco de retardo, sabiendo lo que sucede en los procesadores con
bifurcaciones retardadas (tarda un ciclo más en bifurcar realmente), vamos a intentar reordenar nuestro
fragmento de código para que su ejecución se produzca con la misma semántica que la esperada en un
procesador sin saltos retardados.
Así, después de la instrucción de salto vamos a poner una de las instrucciones que hay antes de la de
salto, es decir, una instrucción que queremos que se ejecute siempre (se produzca el salto o no).
Así, suponiendo que no se altera la semántica del programa, podríamos mover la instrucción I4 y ponerla
justo a continuación de I6 (la bifurcación condicional), quedando entonces la secuencia de instrucciones en
memoria así:
I1, I2, I3, I5, I6, I4, I7, I8, I9, I10, I11, I12, ...
Ahora vamos a ejecutar este programa en nuestro procesador con bifurcaciones retardadas de un ciclo.
Cuando no se produce la bifurcación, la secuencia de instrucciones que se ejecuta es:
I1 – I2 – I3 – I5 – I6 – I4 – I7 – I8 – I9 – I10 – I11 – I12 –...
Si la bifurcación tiene lugar, las instrucciones se ejecutarán en este orden:
I1 – I2 – I3 – I5 – I6 – I4 –I11 – I12 –...
Si no hubiese bifurcación retardada, al alimentar una instrucción de salto condicional habría que detener la
alimentación de instrucciones hasta que la instrucción de salto pasase por la etapa de decodificación (hasta
saber cuál es la siguiente instrucción a ejecutar). Con bifurcación retardada se aprovechan ese ciclo
perdido en alimentar y ejecutar una instrucción que se desea ejecutar incondicionalmente antes de que la
instrucción de bifurcación tenga lugar (se salte o no).
Arquitectura de Computadores
Segmentación (Pipeline) - 40
Esta técnica de los saltos retardados requiere la colaboración del compilador, que debe saber
cómo reorganizar el código para rellenar los huecos de retardo con instrucciones útiles (de la
misma manera que se hacía con las dependencias de datos).
Si el compilador no encuentra una manera de reordenar el código sin afectar a su semántica, debe
insertar tantas operaciones NOP con huecos de retardo como sean necesarias para las
bifurcaciones de ese procesador.
Arquitectura de Computadores
Segmentación (Pipeline) - 41
El problema de los saltos: Predicción del salto.
Otra técnica para reducir el problema de las bifurcaciones consiste en intentar predecir si una
instrucción de bifurcación saltará o no. Por ejemplo, una bifurcación al final de un bucle salta al
comienzo de éste todas las veces excepto la última. Según esto, sería ventajoso que cuando el
procesador se encuentra una instrucción de salto suponga que el salto sí se va a efectuar
realmente, y cuando la etapa de alimentación detecte una bifurcación, empiece a extraer
instrucciones de la dirección de destino del salto. Si una vez que se ejecuta la instrucción de
bifurcación, resulta que efectivamente se salta, la ejecución continúa normalmente, pues las
instrucciones de la dirección del salto son las que ya se están alimentando. Si por el contrario
resulta que no se realiza el salto, se debe vaciar el pipeline (desechar todas las instrucciones
alimentadas) y empezar a alimentar las instrucciones que siguen en secuencia.
Arquitectura de Computadores
Segmentación (Pipeline) - 42
Si ahora suponemos que el control del bucle se realiza mediante una instrucción al comienzo del
mismo, ahora lo normal será suponer que la bifurcación no se tomará hasta la última pasada del
bucle. Es decir, hay veces que conviene suponer una cosa y otras veces otra.
Esto que hemos visto se denomina ejecución especulativa, pues las instrucciones pueden
empezar a ejecutarse antes de que el procesador sepa que las instrucciones alimentadas son las
realmente correctas. Supongamos que se predice que el salto tendrá lugar, por lo que se
empiezan a alimentar instrucciones y a pasarlas a las siguientes etapas del pipeline antes de que
la instrucción de bifurcación finalice su etapa de ejecución. ¡Y si al ejecutar la bifurcación no se
realiza el salto! ¡Nos encontramos que algunas instrucciones ya se han empezado a ejecutar en
las etapas anteriores!
Con ejecución especulativa se debe tener cuidado de que en las etapas anteriores a la de
ejecución no se modifiquen registros o posiciones de memoria hasta que no se confirme que la
predicción realizada ha sido la acertada.
Arquitectura de Computadores
Segmentación (Pipeline) - 43
La repetición de la bifurcación que se toma en el bucle la puede detectar el compilador y
establecer la predicción mediante un cierto código de operación en la bifurcación, lo que quiere
decir que, en ejecución, siempre que se alimente esa instrucción de bifurcación se hará la misma
predicción. Por esto, se la conoce como predicción estática.
La predicción se puede mejorar si se realiza dinámicamente, para lo cual el hardware del
procesador debe establecer la posibilidad de que haya o no salto cada vez que se encuentre una
cierta instrucción de bifurcación. Para ello, en la CPU se debe llevar la cuenta de los resultados de
las últimas ejecuciones de cada bifurcación. Ciertos estudios estadísticos dicen que conservando
solamente el resultado de la última ejecución (con un bit) ya se obtiene una gran probabilidad de
acierto (del orden del 90%) y con la historia de las cuatro últimas ejecuciones (dos bits) se mejora
ligeramente; manteniendo una historia mayor, la mejora es despreciable.
Arquitectura de Computadores
Segmentación (Pipeline) - 44
Como hemos visto, la predicción ante un salto puede ser “saltar” o “no saltar”, para decidir por
dónde continúa extrayendo instrucciones.
Para la arquitectura MIPS, con una porción de código concreta y con los mismos datos, vamos a
ver lo que sucede cuando la predicción es “saltar” y, posteriormente, cuando es “no saltar”.
Predicción NO SALTAR: Después de alimentar la instrucción de salto, se continúa con la
Decodificación, pero hasta el final de esta etapa no se sabrá si habrá que saltar o no. Como la
predicción es “no saltar”, en la etapa F se alimenta la siguiente instrucción en memoria. Al final de
la etapa D se ve que se ha cometido un fallo en la predicción, por lo que habrá que eliminar la
instrucción daddi del cauce y empezar a extraer y ejecutar la instrucción de destino del salto. Se
ha perdido un ciclo de tiempo.
Predicción SALTAR: Igual que antes, después de alimentar la instrucción de salto, se continúa
con la Decodificación. Como la predicción es “saltar”, en este ciclo se querría extraer la siguiente
instrucción de la dirección del salto, pero como hasta el final de la Decodificación tampoco se
conoce la dirección del salto, en este ciclo no se puede extraer ninguna instrucción, y hay que
esperar al final de la Decodificación para poder alimentar la instrucción de destino del salto.
Como vemos, en MIPS, ante una condición que establezca que se debe saltar, se pierde un ciclo
tanto con una predicción de “saltar” como de “no saltar”. Por el contrario, si la condición se
evaluara como falsa (no saltar), con la predicción de NO SALTAR, sí se podría seguir alimentando
instrucciones de la siguiente dirección de memoria.
Por lo que hemos visto, en MIPS 64 no tiene ninguna utilidad predecir “Tomar el salto”, por lo que
su predicción es NO SALTAR.
Arquitectura de Computadores
Segmentación del Cauce - 45
Las instrucciones de sumas y restas en aritmética entera pueden pasar por la etapa de ejecución
realizando su cometido en un solo ciclo, pero hay otras operaciones, como la multiplicación, la
división o, en general, las instrucciones con datos de coma flotante, que no pueden ejecutar la
operación de la etapa E en un solo ciclo.
En este capítulo vamos a ver posibles soluciones a esta cuestión y también los problemas que van
a surgir.
Arquitectura de Computadores
MIPS64 - 46
La arquitectura de MIPS64 que hemos visto hasta ahora solamente contempla la aritmética con
enteros, pero los procesadores deben soportar también operaciones en coma flotante.
Las operaciones en coma flotante requieren bastante más tiempo en ejecutarse que las de la
aritmética entera, pueden durar de un par de ciclos, como la negación; hasta más de un centenar,
como la raíz cuadrada.
Para adaptar esto a nuestro pipeline, una opción sería alargar el tiempo del ciclo de reloj hasta el
necesario para ejecutar las instrucciones de coma flotante, pero esto no sería práctico, porque
estaríamos ralentizando la ejecución del resto de las instrucciones, haciéndolas tan lentas como
las de coma flotante.
Otra posibilidad podría consistir en mantener la duración del tiempo de ciclo de los enteros y
repetir la etapa de Ejecución tantas veces como fuera necesario como para ejecutar cada
instrucción de coma flotante. Esto también tendría la pega de que la ejecución de una instrucción
de coma flotante pararía el cauce hasta su terminación.
Este último problema se puede solucionar si dotamos al cauce de múltiples unidades funcionales
en la etapa E: una para enteros, otras para multiplicación, división, para aritmética en coma
flotante, etc. Veámoslo en la siguiente página
Arquitectura de Computadores
Segmentación del Cauce - 47
Nuestro nuevo cauce podría tener:
•  Una unidad principal de enteros, cargas y almacenamientos, y gestión de bifurcaciones.
•  Multiplicador de enteros y coma flotante.
•  Sumas y restas y conversiones en coma flotante.
•  División de enteros y coma flotante.
Disponiendo de varias unidades funcionales, si comienza a ejecutarse una operación de coma
flotante, no impediría que pasara a ejecutarse una instrucción posterior de suma de enteros.
Pero seguimos teniendo un problema. Si a una operación en coma flotante (que es de larga
duración) le sigue otra más también en coma flotante, esta última tendrá que esperar a que
termine completamente la primera para comenzar su ejecución. Esta es la situación que teníamos
antes de inventar el pipeline.
La solución a este problema es obvia: hay que segmentar las unidades de coma flotante en
múltiples etapas, cada una de un ciclo de duración. Así, un ciclo después del comienzo de una
instrucción en la unidad de coma flotante podría arrancar la siguiente instrucción, aunque también
sea de coma flotante.
Como vemos en el gráfico de nuestro ejemplo, las instrucciones que se ejecuten en la unidad de
enteros, E, solamente requieren un ciclo; las sumas en coma flotantes se ejecutaran en 4 ciclos; y
las multiplicaciones, en 7.
La unidad funcional de las divisiones no está segmentada, y requiere 24 ciclos de reloj para
ejecutarse. Ya que esta última unidad funcional no está segmentada, se pueden producir riesgos
estructurales si hay dos divisiones consecutivas o cercanas en el tiempo.
Arquitectura de Computadores
Segmentación del Cauce - 48
Ya que las unidades funcionales multiciclo duran más, hay mayores riesgos, estructurales y de
datos. Comencemos viendo los riesgos estructurales.
El caso de la división es muy claro. Dadas dos instrucciones de división consecutivas, la segunda
no puede arrancarse un ciclo después de la primera, pues su unidad funcional no está
segmentada, por lo que antes de emitir la segunda división (pasarla a su etapa de ejecución),
debe esperar en D a que finalice la etapa E de la división anterior.
Arquitectura de Computadores
Segmentación del Cauce - 49
Ya que algunas instrucciones duran más ciclos que otras, en un momento dado puede haber
varias instrucciones en el cauce que lleguen a un punto en el que intenten ejecutar la misma etapa
al mismo tiempo.
Este es el caso del ejemplo de arriba, en el que tres instrucciones compiten por la etapas M y W.
Obsérvese que aunque las 3 instrucciones llegan al mismo tiempo a la etapa M, solamente la
instrucción de carga la utiliza realmente (las otras dos utilizan registros), por lo que el acceso a
memoria no supone un riesgo estructural.
En cambio, sí compiten realmente por utilizar W, la etapa de escritura de registros, pues todas
ellas generan un resultado cuyo destino es un registro.
Veamos cómo se soluciona este conflicto en la página siguiente.
Arquitectura de Computadores
Segmentación del Cauce - 50
Ante el problema estructural de la competencia por un recurso, se podría disponer de un conjunto
de registros generales con más de un puerto de escritura, pero esto no suele hacerse, pues estas
situaciones solamente se producen en muy raras ocasiones.
Normalmente, la solución adoptada consiste en detectar la situación del riesgo estructural
(competencia por el recurso), detener las instrucciones que compiten y dejarlas continuar en
secuencia, de una en una.
Lo razonable es dejar continuar la instrucción de mayor latencia (la que genera mayor retardo),
pues mientras no avance, es la que tiene más probabilidades de causar más riesgos de datos a
otras instrucciones. El resto de las instrucciones que compiten por entrar en la etapa W, pueden
quedar detenidas en la etapa M, a la espera de que W quede libre. En el caso de MIPS, estas
instrucciones quedan detenidas en la etapa D, que es donde se comprueban los riesgos
estructurales.
Arquitectura de Computadores
Segmentación del Cauce - 51
Ya habíamos visto los problemas que se pueden producir debidos a dependencias entre
operaciones cercanas en el tiempo. Ahora, estos problemas se pueden complicar con las
operaciones multiciclo y, además, veremos que aparecen nuevos tipos de riesgos de datos. Las
dependencias entre instrucciones pueden ser de datos y de nombre.
Las dependencias de datos son las que existen entre dos instrucciones que comparten datos.
Las dependencias de nombre se producen cuando dos instrucciones utilizan el mismo registro o
dirección de memoria (comparten el nombre), pero realmente no hay flujo de datos entre tales
instrucciones (no comparten el dato). Hay dos tipos de dependencias de nombre:
antidependencias y dependencias de salida.
Los distintos tipos de dependencias pueden dan lugar a otros tantos tipos de “riesgos de
datos” (RAW, WAR y WAW). La verdadera dependencia de datos (RAW) viene dada por la propia
lógica del programa, mientras que las otras dos pueden producirse dependiendo de las
características del cauce del procesador.
•  Verdadera dependencia de datos àRAW (Read After Write)
•  Dependencia de salida
à WAW (Write After Write)
•  Antidependencia
à WAR (Write After Read)
En los cauces con operaciones multiciclo, además de que las dependencias RAW se pueden
producir más fácilmente, también aparecen los otros dos tipos de dependencias.
Arquitectura de Computadores
MIPS64 - 52
Los riesgos RAW de las dependencias entre instrucciones ya los teníamos en los cauces
convencionales, pero ahora, con las operaciones multiciclo, se van a producir con mayor facilidad.
En el ejemplo de arriba, las dos operaciones de suma y almacenamiento de enteros pueden
ejecutarse sin ningún problema pese a que tienen la dependencia de R1, ya que, como ya
sabemos, aunque la escritura y la lectura de R1 coinciden en el mismo ciclo, la suma puede
escribir el resultado en R1 en el primer subciclo, y el almacenamiento puede leer R1 en el
segundo subciclo.
En el caso de que estas operaciones fueran con datos en coma flotante, las cosas cambian. La
etapa de ejecución de la suma ahora requiere más tiempo, por lo que es multiciclo; así que dura 3
ciclos más que con los enteros. Ahora nos encontramos que cuando la operación de
almacenamiento llega a la etapa D para leer el operando R1, la suma todavía no ha llegado a la
etapa W de escritura del resultado en R1.
Ante este escenario, la solución de MIPS es parar la instrucción de almacenamiento en la etapa D,
hasta que el operando R1 quede actualizado en la etapa W de la suma.
Arquitectura de Computadores
Segmentación del Cauce - 53
En el pipeline que habíamos visto hasta hace poco, cuando solamente operábamos con enteros,
varias instrucciones se ejecutaban simultáneamente en el cauce, e iban finalizando en el mismo
orden en el que comenzaban.
Con la incorporación de la coma flotante y la necesidad de incluir unidades funcionales multiciclo,
nos encontramos con el escenario de la parte inferior de la figura, en el que ¡las instrucciones no
finalizan en el mismo orden en el que se emiten!
La multiplicación en coma flotante es una operación multiciclo muy larga, por lo que finaliza
después de su sucesora, una suma, también en coma flotante, pero 2 ciclos más corta. La suma
en coma flotante también es unos ciclos más larga que la carga y el almacenamiento que le
siguen, por lo que, igualmente, también finaliza más tarde que éstas.
Esto va a generar unas nuevas formas de dependencias de datos que veremos en las siguientes
páginas.
Arquitectura de Computadores
MIPS64 - 54
La dependencia de salida (Write After Write) se produce cuando una instrucción i y una instrucción
j escriben su resultado en el mismo registro o dirección de memoria. Se debe preservar el orden
de ejecución entre las dos instrucciones para asegurar que el valor que se escribe finalmente es el
que corresponde a la instrucción j.
En el fragmento de programa de arriba, vemos que debido a la gran latencia de la multiplicación,
ésta finaliza después de la suma, aún empezando ésta posteriormente. Así, el valor que al final
queda escrito en el registro F1 es el que deja la multiplicación, lo cual es incorrecto.
Obsérvese que este efecto solamente se produce cuando el resultado de la multiplicación se
escribe (en F1) sin que ninguna instrucción lo utilice posteriormente como operando de entrada.
Así, si entre la multiplicación y la suma hubiese una resta, por ejemplo, que tomase el resultado de
la multiplicación como dato de entrada, ya no se produciría el problema pues al impedir el riesgo
RAW entre la multiplicación y la resta, se evita también el riesgo WAW entre la multiplicación y la
suma.
Obsérvese que la suma no puede entrar en la etapa D hasta que la resta pasa a la etapa de
ejecución.
Arquitectura de Computadores
Segmentación del Cauce - 55
En general, hay dos soluciones para los riesgos de datos de tipo WAW:
1.  Desechar el resultado de la multiplicación, puesto que no se va a utilizar por ninguna
instrucción. O lo que es más fácil, sustituir la instrucción de multiplicación por una NOP (no
Operación).
2.  Detener la emisión de la suma (la instrucción posterior), hasta que la multiplicación (la anterior)
entre en la etapa de Memoria. De esta manera se asegura que la instrucción posterior no
escribirá el registro de destino antes que la anterior instrucción. Esta es la solución de MIPS a
los riesgos WAW.
No olvidemos tampoco, que este tipo de problemas solo se pueden producir con programas en los
que hay secuencias de instrucciones sin sentido, es decir, instrucciones que producen resultados
que no son utilizados por ninguna instrucción posterior.
Arquitectura de Computadores
Segmentación del Cauce - 56
Los riesgos debidos a las antidependencias (WAR) entre dos instrucciones i y j se producen
cuando la instrucción j escribe un registro o posición de memoria que la instrucción i utiliza como
operando de entrada.
En el ejemplo de arriba (en un procesador hipotético), si debido a una ejecución fuera de orden, la
resta se ejecuta antes que la suma, el operando de entrada F8 de la suma, no sería el correcto,
pues tomaría el valor producido como resultado de la resta, lo cual no era lo pretendido en el
programa original.
Estos problemas se producen en las arquitecturas con planificación dinámica, que conllevan
ejecución fuera de orden. También podría producirse en pipelines en los que, por algún motivo, se
produzcan lecturas en etapas finales y escrituras al comienzo del cauce.
Arquitectura de Computadores
Segmentación del Cauce - 57
Los riesgos WAR no se producen en nuestro procesador MIPS, ya que utiliza un cauce estático,
es decir, que se respeta el orden de emisión o arranque de las instrucciones.
Obsérvese que aunque la finalización de las instrucciones sí puede producirse fuera de orden, la
emisión o arranque sí se realiza en orden. Además, los operandos se leen enseguida, en la etapa
D, y las escrituras en registros se producen al final, en W.
En este ejemplo, la instrucción de suma deja el resultado en F2, que lo utiliza la instrucción
anterior como operando de entrada. Aunque la suma finaliza antes que la multiplicación, no afecta
a la ejecución correcta, porque la multiplicación toma le valor de F2 en su etapa D, mucho antes
de la etapa W de la suma.
Arquitectura de Computadores
Segmentación del Cauce - 58