Laboratorul 09 - Pipelining

Obiective

În acest laborator vom învăța cum funcționează un pipeline pentru instrucțiuni și vom implementa un pipeline simplu.

1. Fazele execuției unei instrucțiuni

Execuția unei singure instrucțiuni poate fi împărțită în mai multe etape. Acestea pot fi oricât de multe sau puține și pot avea diverse funcții. O împărțire comună, utilizată de primele procesoare RISC, are 5 stagii:

  • Instruction fetch (IF): Instrucțiunea este adusă din memorie într-un registru de procesor.
  • Instruction decode (ID): Instrucțiunea adusă anterior este decodificată și sunt determinate unitățile și datele necesare execuției.
  • Execute (EX): Acțiunea determinată anterior este executată.
  • Memory access (MEM): Dacă este necesar, se accesează memoria de date.
  • Register write back (WB): Dacă este necesar, se scriu date în registrele de procesor.

O instrucțiune nu trebuie să folosească toate stagiile disponibile. Spre exemplu, pe o arhitectura load/store, doar instrucțiunile load și store accesează memoria, deci folosesc etapa MEM, restul folosesc doar registrele de procesor, deci folosesc etapa WB.

2. Pipelining

Execuția normală a instrucțiunilor are loc în mod secvențial. Deci execuția unei instrucțiuni va începe după terminarea ultimului stagiu al instrucțiunii precedente. Astfel, considerând modelul descris mai sus, pentru a executa 3 instrucțiuni avem nevoie de 15 cicli de ceas (considerând că toate instrucțiunile folosesc toate stagiile).

Execuția fără pipeline

A executa instrucțiuni în pipeline înseamnă că există câte o instrucțiune în fiecare stagiu la orice moment dat. În timp ce o instrucțiune este în stagiul de execuție, alta este decodificată, alta este adusă din memorie etc. Asta duce la o reducere semnificativă a timpului de execuție al unui program. În același timp în care puteam executa doar 3 instrucțiuni fără pipeline, putem executa 11 instrucțiuni în pipeline.

Execuția în pipeline

Execuția fiecărei instrucțiuni în parte durează la fel de mult ca în varianta fără pipeline, însă execuția programului durează mai puțin.

Trebuie totuși să aplicăm o constrângere asupra stagiilor: fiecare stagiu trebuie să dureze la fel de mult timp.

3. Hazarde

O problemă introdusă de execuția în pipeline sunt hazardele. Un hazard este situația în care execuția următoarei instrucțiuni este imposibilă sau va produce rezultate greșite.

3.1. Hazarde de date

Un hazard de date se produce atunci când o instrucțiune are nevoie de un rezultat într-un stagiu al execuției, iar acel rezultat este calculat într-un stagiu al altei instrucțiuni ce va fi executat mai târziu.

Hazard Exemplu Registru afectat
Read After Write (RAW)
R2 <- R1 + R3
R4 <- R2 + R2
R2
Write After Read (WAR)
R4 <- R1 + R5
R5 <- R1 + R2
R5
Write After Write (WAW)
R2 <- R4 + R7
R2 <- R1 + R3
R2

Aceste hazarde sunt rezolvate prin bubbling, execuția out-of-order sau pasarea operanzilor înainte (forwarding).

Hazardele WAR si WAW pot apărea numai în medii de execuție concurente (e.g. procesoare multi-core).

3.2. Hazarde structurale

Hazardele structurale au loc atunci când două instrucțiuni trebuie să folosească aceeași unitate hardware în același timp. Un astfel de hazard are loc, spre exemplu, într-un sistem cu o singură memorie când se încearcă citirea unei instrucțiuni în același timp cu scrierea rezultatului altei instrucțiuni în aceeași memorie.

Aceste hazarde sunt rezolvate prin separarea unităților astfel încât această situație să nu se poată întâmpla (e.g. separarea memoriei de instrucțiuni față de memoria de date) sau prin bubbling.

3.3. Hazarde de control

Hazardele de control au loc după instrucțiunile de salt condițional. Problema aici este că procesorul trebuie să încarce următoarea instrucțiune înainte de a ști rezultatul saltului, deci s-ar putea să încarce instrucțiunea greșită. În cazul unei predicții eronate, instrucțiunea încărcată trebuie eliminată din pipeline.

Aceste hazarde pot fi rezolvate prin bubbling, dar de obicei sunt tratate de unități specializate de branch prediction.

4. Exerciții

Vom implementa un procesor extrem de simplu, ce execută instrucțiuni în 5 stagii: IF, ID, EX, MEM și WB.

Task 0 și Task 1 presupun realizarea unui procesor ce va fi capabil să execute următoarele instrucțiuni din memoria de program:

xor r0, r1
and r2, r3
or r4, r5
neg r6           
add r7, r8
sub r7, r2
mod r7, r2
div r20, r2
mul r4, r2
div r6, r4
add r0, r7 
add r4, r6
add r2, r20
add r0, r4
add r0, r2

Task 0 (3p) - Implementați logica procesorului făra pipeline în task01.v.

  • Urmariți TODO-urile.
  • Notați numărul de cicluri de ceas până la terminarea ultimei instrucțiuni din task02.v (memoria pentru instrucțiuni).

Structura unei instrucțiuni este următoarea: Cod instrucțiune (16 biți) | operand 1 (8 biți) | operand 2 (8 biți), unde fiecare operand reprezintă indexul unui registru.

e.g. instrucțiunea ADD R1, R1 este codificată astfel: 0000000000000001 00000001 00000001.

Instrucțiunea NEG are un singur operand (operand 1).

Task 1 (6p) - Implementați logica procesorului cu pipeline în task11.v.

  • Urmariți TODO-urile.
  • Observați rezolvarea hazardelor din task12.v.
  • Notați numărul de cicluri de ceas până la terminarea ultimei instrucțiuni din task12.v.

Din moment ce unele componente ale pipeline-ului folosesc aceleași elemente, avem nevoie să reținem într-o altă variabilă elementele scrise de o componentă în ciclul anterior de ceas, de care are nevoie acum componenta următoare.

Variabilele care sunt folosite de componente consecutive ale pipeline-ului trebuie reținute și într-un registru special buffered. Spre exemplu, între memory și decoder avem variabila instruction folosită de ambele. Într-un ciclu de ceas, memory scrie în instruction, dar decoder are nevoie de valoarea de la ciclul de ceas anterior. Pentru aceasta, decoder va primi variabila instruction_buffer, care are valoarea lui instruction din ciclul anterior de ceas.

Task 2 (2.5p) - Completați memoria de program din task21.v astfel încât la final să se afle în registrul 0 valorea în zecimal 1234. (HINT: Ce valori se află inițial în registrele procesorului?)

Task 3 (0.5p) - Completați memoria de program din task31.v astfel încât la final să se afle în registrul 0 valorea în zecimal 1234. Fiți atenți la posibile hazarde!

Când simulați orice task se va deschide fișierul Default.wcfg. Acesta are toate semnalele incluse pentru debugging. Aveți grijă să nu suprascrieți Default.wcfg când ieșiți din simulator.

Resurse

soc/laboratoare/09.txt · Last modified: 2024/05/09 10:04 by stefan.jumarea
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0