Un riesgo RAW (read after write, lectura después de escritura) aparece cuando una instrucción lee un registro que una instrucción anterior todavía no ha escrito. En un pipeline de 5 etapas es el riesgo más frecuente, porque los registros se leen en ID pero se escriben en WB, tres etapas después.
El problema: sub necesita x1 antes de tiempo
add x1, x2, x3 sub x4, x1, x5
add calcula x1 en EX (ciclo 3) y lo escribe en WB (ciclo 5). sub lee sus registros en ID en el ciclo 3. Si no se hace nada, leería el valor antiguo de x1. La solución más sencilla es detener sub en ID hasta que el valor esté en el banco de registros:
| Etapa | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| IF | I0 | I1 | — | — | — | — | — | — | — |
| ID | — | I0 | I1 | I1 | I1 | I1 | — | — | — |
| EX | — | — | I0 | burb. | burb. | burb. | I1 | — | — |
| MEM | — | — | — | I0 | burb. | burb. | burb. | I1 | — |
| WB | — | — | — | — | I0 | burb. | burb. | burb. | I1 |
En el simulador, sin forwarding, sub espera hasta que add sale de WB: 3 burbujas. Muchos libros, entre ellos Patterson y Hennessy, suponen que el banco de registros se escribe en la primera mitad del ciclo y se lee en la segunda; con esa hipótesis sub puede leer en el mismo ciclo del WB y la espera es de 2 burbujas. El simulador no modela esa división del ciclo, así que sus cifras sin forwarding son una unidad mayores.
Forwarding: usar el resultado antes de escribirlo
El resultado de add ya existe al final de su EX, guardado en el registro de segmentación EX/MEM. El forwarding añade multiplexores a la entrada de la ALU para tomar el operando directamente de ahí en lugar del banco de registros:
| Etapa | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| IF | I0 | I1 | — | — | — | — |
| ID | — | I0 | I1 | — | — | — |
| EX | — | — | I0 | I1 | — | — |
| MEM | — | — | — | I0 | I1 | — |
| WB | — | — | — | — | I0 | I1 |
Sin burbujas. Si entre las dos instrucciones hay otra, el valor se toma un ciclo más tarde, del registro MEM/WB. Estos son los dos caminos de forwarding del pipeline clásico:
- EX/MEM → EX: la productora está justo delante (distancia 1).
- MEM/WB → EX: hay una instrucción en medio (distancia 2).
add x1, x2, x3 addi x9, x9, 1 sub x4, x1, x5
Con forwarding esta secuencia no tiene burbujas; sin forwarding, el simulador inserta 2 (compruébalo).
La excepción: carga seguida de uso
lw x1, 0(x2) add x3, x1, x4
Una carga no tiene el dato al final de EX, sino al final de MEM, porque en EX solo calcula la dirección. add lo necesita al principio de su EX, que sin espera coincidiría con el MEM de lw. Ningún camino de forwarding puede enviar un dato hacia atrás en el tiempo, así que hace falta una burbuja; después el valor llega desde MEM/WB:
| Etapa | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| IF | I0 | I1 | — | — | — | — | — |
| ID | — | I0 | I1 | I1 | — | — | — |
| EX | — | — | I0 | burb. | I1 | — | — |
| MEM | — | — | — | I0 | burb. | I1 | — |
| WB | — | — | — | — | I0 | burb. | I1 |
La unidad de detección de riesgos lo detecta en ID: si la instrucción que está en EX es una carga y su rd coincide con rs1 o rs2 de la que está en ID, detiene el pipeline un ciclo. Eso es lo que implementa el simulador.
Evitar la burbuja reordenando
Si hay una instrucción independiente cerca, se puede colocar entre la carga y su uso. Esta secuencia tiene una burbuja de carga-uso entre lw y add:
| Etapa | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| IF | I0 | I1 | I2 | I2 | I3 | — | — | — | — |
| ID | — | I0 | I1 | I1 | I2 | I3 | — | — | — |
| EX | — | — | I0 | burb. | I1 | I2 | I3 | — | — |
| MEM | — | — | — | I0 | burb. | I1 | I2 | I3 | — |
| WB | — | — | — | — | I0 | burb. | I1 | I2 | I3 |
addi no depende de lw, así que la adelantamos:
| Etapa | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| IF | I0 | I1 | I2 | I3 | — | — | — | — |
| ID | — | I0 | I1 | I2 | I3 | — | — | — |
| EX | — | — | I0 | I1 | I2 | I3 | — | — |
| MEM | — | — | — | I0 | I1 | I2 | I3 | — |
| WB | — | — | — | — | I0 | I1 | I2 | I3 |
9 ciclos pasan a 8, el mínimo para 4 instrucciones. El resultado del programa no cambia porque addi no lee ni escribe t0 ni t1. Los compiladores hacen esta planificación de instrucciones de forma automática.
¿Y los riesgos WAR y WAW?
Escritura después de lectura (WAR) y escritura después de escritura (WAW) no aparecen en este pipeline: las instrucciones avanzan en orden, todas leen sus registros en ID y todas escriben en WB. Solo importan en procesadores que ejecutan fuera de orden.
Para practicar
El ejercicio de carga-uso pide contar ciclos con y sin forwarding, y el de reordenación comprueba la última sección.