Diferență între revizuiri ale paginii „Laboratorul 6”
(Nu s-au afișat 24 de versiuni intermediare efectuate de același utilizator) | |||
Linia 12: | Linia 12: | ||
[[Fișier: asc_lab5_pipeline_fw_cmp.png]] | [[Fișier: asc_lab5_pipeline_fw_cmp.png]] | ||
+ | |||
+ | Rezultatul comparării este însă validat numai dacă instrucțiunea aflată în execuție trebuie să scrie în registrul destinație (adică dacă '''regs_wen''' este activ) și numai dacă instrucțiunea aflată în READ citește din registrul sursă. Instrucțiunile ''ADD'', ''SUB'', ''LOAD'' și ''STORE'' citesc operandul 1 din registre, iar operandul 2 este citit numai de instrucțiunile ''ADD'', ''SUB'' și ''STORE''. | ||
+ | Așadar logica de generare a semnalului '''fw1''' poate fi descrisă astfel: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | // ADD SUB LOAD STORE | ||
+ | assign fw1 = (r2_dest == r1_sursa1) & regs_wen & ((r1_opcode == 4'b0001) | (r1_opcode == 4'b0010) | (r1_opcode == 4'b1001) | (r1_opcode == 4'b1010)); | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | În mod asemănător se descrie generarea semnalului '''fw2''': | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | assign fw2 = // <<<<< COMPLETAȚI CODUL ! | ||
+ | </syntaxhighlight> | ||
=== Avansarea rezultatului === | === Avansarea rezultatului === | ||
Linia 19: | Linia 33: | ||
[[Fișier: asc_lab5_pipeline_fw_mux.png]] | [[Fișier: asc_lab5_pipeline_fw_mux.png]] | ||
+ | Dacă '''fw1''' este activ, se selectează pentru operandul 1 rezultatul de la ieșirea nivelului de execuție. Analog se face selecția pentru operandul 2: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | assign operand1 = fw1 ? result : rdata1; | ||
+ | assign operand2 = (r1_opcode == 4'b1000) ? r1_instr_data : fw2 ? // <<<<< COMPLETAȚI CODUL ! | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === Rularea unui program cu dependențe de date === | ||
+ | |||
+ | Pentru testarea calculatorului se modifică programul anterior eliminând instrucțiunile NOP dintre instrucțiunile ce folosesc una rezultatul alteia: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | 0 LOADC R0 #255 | ||
+ | 1 LOADC R1 #254 | ||
+ | 2 LOADC R2 #253 | ||
+ | 3 LOAD R4 R0 | ||
+ | 4 LOAD R5 R1 | ||
+ | 5 ADD R6 R5 R4 | ||
+ | 6 STORE R2 R6 | ||
+ | 7 HALT | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | == Salturi în pipeline == | ||
− | + | Procesarea instrucțiunilor de salt în pipeline necesită detecția și gestionarea dependețelor de control deoarece decizia de execuție sau nu a salturilor are loc într-una din etapele de procesare ulterioare extragerii instrucțiunii. Pipeline-ul simplu din Laboratorul 4 va executa instrucțiunea de salt în etapa de execuție. La sfârșitul etapei de execuție a instrucțiunii de salt (pe frontul de ceas) contorul de program se încarcă cu adresa de salt iar instrucțiunile ce au intrat sau intră în pipeline după instrucțiunea de salt sunt anulare, înlocuindu-se cu instrucțiuni ''NOP''. | |
[[Fișier: asc_lab5_procesor_jmp.png]] | [[Fișier: asc_lab5_procesor_jmp.png]] | ||
+ | |||
+ | În acest laborator setul simplu de instrucțiuni RISC se va completa cu o instrucțiune de salt necondiționată la o adresă specificată în corpul instrucțiunii, ''JMP #target''. | ||
+ | Codul instrucțiunii de salt se alege 'b1100, iar adresa de salt ocupă octetul inferior al instrucțiunii (la fel ca și constanta instrucțiunii ''LOADC''. | ||
+ | |||
+ | |||
+ | === Detecția dependenței de control === | ||
+ | |||
+ | Constă în detecția instrucțiunii de salt în etapa de execuție. Instrucțiunea de salt nu scrie nimic în setul de registre și nici în memorie. Logica de generare a semnalelor ''regs_wen'' și ''write'' trebuie să genereze 0 dacă instrucțiunea curentă din execuție este ''JMP''. | ||
+ | Nivelul de execuție se completează cu logica generării semnalului de control ''pc_load '' ce va determina actualizarea PC și înlocuirea cu ''NOP'' a instrucțiunilor mai noi ce deja au intrat în pipeline: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | assign pc_load = r2_opcode == 4'b1100; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | === Actualizarea stării procesorului === | ||
+ | |||
+ | La execuția instrucțiunii ''JMP'' contorul de program, PC, se va încărca cu adresa de salt. Descrierea PC din laboratorul precedent se completează cu o ramură corespunzătoare condiționată de semnalul '''pc_load ''': | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | always @(posedge clk) begin | ||
+ | if(rst) | ||
+ | pc <= 0; | ||
+ | else if(halt) | ||
+ | pc <= pc; | ||
+ | else if(pc_load ) | ||
+ | pc <= pc_target; | ||
+ | else | ||
+ | pc <= pc + 1; | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Adresa de salt este byte-ul inferior al instrucțiunii. Când instrucțiunea de salt ajunge în nivelul de execuție, byte-ul inferior este accesibil ca operand 2: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | assign pc_target = r2_operand2; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | De asemenea, pentru ca acest byte să ajungă în întregime ca operand 2 în execuție, operandul 2 al instrucțiunii ''JMP'' trebuie selectat în etapa READ la fel cum este selectat acesta pentru instrucțiunea ''LOADC''. Codul de selecție se modifică astfel: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | assign operand2 = ((r1_opcode == 4'b1000) | (r1_opcode == 4'b1100)) ? r1_instr_data : rdata2; // LOADC sau JMP | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | La sfârșitul execuției instrucțiunii de salt, instrucțiunile mai noi ce sunt deja în nivelurile inferioare de pipeline se înlocuiesc cu instrucțiuni ''NOP''. Astfel registrul pipeline R1 se va încărca cu instrucțiunea ''NOP'' (ce are codul binar 'b0000) dacă semnalul de control '''pc_load ''' este activ: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | always @(posedge clk) begin | ||
+ | if(rst) | ||
+ | r1 <= 0; | ||
+ | else if(halt) | ||
+ | r1 <= r1; | ||
+ | else if(pc_load ) | ||
+ | r1 <= 0; | ||
+ | else | ||
+ | r1 <= instr; | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Analog se completează și descrierea registrului pipeline R2. | ||
[[Fișier: asc_lab5_pipeline_jmp.png]] | [[Fișier: asc_lab5_pipeline_jmp.png]] | ||
+ | |||
+ | === Rularea unui program cu instrucțiuni de salt === | ||
+ | |||
+ | Pentru testarea calculatorului se modifică programul din memoria de program astfel: | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | 0 LOADC R0 #255 | ||
+ | 1 LOADC R1 #254 | ||
+ | 2 LOADC R2 #253 | ||
+ | 3 JMP #7 | ||
+ | 4 ADD R6 R5 R4 | ||
+ | 5 STORE R2 R6 | ||
+ | 6 HALT | ||
+ | 7 LOAD R4 R0 | ||
+ | 8 LOAD R5 R1 | ||
+ | 9 JMP #4 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Activități suplimentare == | ||
+ | |||
+ | === Activitatea 1 === | ||
+ | |||
+ | Implementați o instrucțiune de salt condiționată, ''JMPZ #target''. Saltul se va executa ('''pc_load ''' va fi activat) numai dacă registrul indicator Z (actualizat după execuția instrucțiunii precedente) este 1. Procesorul pipeline se va completa cu un registru pentru indicatori actualizat pe ceas ce salvează în permanență indicatorii ('''flags''') de la ieșirea ALU. Codul instrucțiunii ''JMPZ'' se alege 'b1101. | ||
+ | |||
+ | |||
+ | === Activitatea 2 === | ||
+ | |||
+ | Modificați implementarea detecției și gestiunii salturilor necondiționate, ''JMP #target'', astfel încât saltul în program (încărcarea adresei de salt în PC) să aibă loc la sfârșitul etapei READ. În felul acesta se va pierde o singură instrucțiune și nu două. |
Versiunea curentă din 18 decembrie 2020 09:47
Pipeline cu avansarea datelor
Procesorul implementat in laboratorul 4 are numai trei etaje pipeline. Singura dependență de date ce poate apare este cea de tip RAW între instrucțiunea aflată în etapa de EXECUTE și instrucțiunea imediat următoare din etapa de READ, dacă aceasta din urmă folosește rezultatul primeia. Pentru evitarea hazardului de date este necesară gestiunea dependențelor de tip RAW. În laboratorul 4 dependența se elimina din program prin adăugarea unei instrucțiuni NOP între cele două instrucțiuni ce depind una de alta. Această metodă simplă lungește timpul de execuție al programului. Alternativa avansării datelor elimină acest neajuns, dar necesită detecția și gestiunea în HW a dependențelor. Calea de avansare permite citirea operandului direct de la instrucțiunea precedentă, de la ieșirea nivelului EXECUTE.
Detecția dependențelor RAW
Dependența de date de tip RAW poate apare numai între instrucțiunea aflată în execuție și instrucțiunea următoare, aflată în faza de citire, dacă rezultatul primeia este folosit ca operand de instrucțiunea imediat următoare, adică registrul destinație al primeia coincide cu unul din registrele sursă ale instrucțiunii următoare. Detecția dependenței se face comparând adresa destinație a instrucțiunii din etapa de execuție cu adresele sursă ale instrucțiunii aflate în etapa de citire.
Rezultatul comparării este însă validat numai dacă instrucțiunea aflată în execuție trebuie să scrie în registrul destinație (adică dacă regs_wen este activ) și numai dacă instrucțiunea aflată în READ citește din registrul sursă. Instrucțiunile ADD, SUB, LOAD și STORE citesc operandul 1 din registre, iar operandul 2 este citit numai de instrucțiunile ADD, SUB și STORE.
Așadar logica de generare a semnalului fw1 poate fi descrisă astfel:
// ADD SUB LOAD STORE
assign fw1 = (r2_dest == r1_sursa1) & regs_wen & ((r1_opcode == 4'b0001) | (r1_opcode == 4'b0010) | (r1_opcode == 4'b1001) | (r1_opcode == 4'b1010));
În mod asemănător se descrie generarea semnalului fw2:
assign fw2 = // <<<<< COMPLETAȚI CODUL !
Avansarea rezultatului
Odată detectată dependența RAW, rezultatul instrucțiunii din etapa de execuție este selectat ca operand în locul valorii registrului sursă.
Dacă fw1 este activ, se selectează pentru operandul 1 rezultatul de la ieșirea nivelului de execuție. Analog se face selecția pentru operandul 2:
assign operand1 = fw1 ? result : rdata1;
assign operand2 = (r1_opcode == 4'b1000) ? r1_instr_data : fw2 ? // <<<<< COMPLETAȚI CODUL !
Rularea unui program cu dependențe de date
Pentru testarea calculatorului se modifică programul anterior eliminând instrucțiunile NOP dintre instrucțiunile ce folosesc una rezultatul alteia:
0 LOADC R0 #255
1 LOADC R1 #254
2 LOADC R2 #253
3 LOAD R4 R0
4 LOAD R5 R1
5 ADD R6 R5 R4
6 STORE R2 R6
7 HALT
Salturi în pipeline
Procesarea instrucțiunilor de salt în pipeline necesită detecția și gestionarea dependețelor de control deoarece decizia de execuție sau nu a salturilor are loc într-una din etapele de procesare ulterioare extragerii instrucțiunii. Pipeline-ul simplu din Laboratorul 4 va executa instrucțiunea de salt în etapa de execuție. La sfârșitul etapei de execuție a instrucțiunii de salt (pe frontul de ceas) contorul de program se încarcă cu adresa de salt iar instrucțiunile ce au intrat sau intră în pipeline după instrucțiunea de salt sunt anulare, înlocuindu-se cu instrucțiuni NOP.
În acest laborator setul simplu de instrucțiuni RISC se va completa cu o instrucțiune de salt necondiționată la o adresă specificată în corpul instrucțiunii, JMP #target.
Codul instrucțiunii de salt se alege 'b1100, iar adresa de salt ocupă octetul inferior al instrucțiunii (la fel ca și constanta instrucțiunii LOADC.
Detecția dependenței de control
Constă în detecția instrucțiunii de salt în etapa de execuție. Instrucțiunea de salt nu scrie nimic în setul de registre și nici în memorie. Logica de generare a semnalelor regs_wen și write trebuie să genereze 0 dacă instrucțiunea curentă din execuție este JMP. Nivelul de execuție se completează cu logica generării semnalului de control pc_load ce va determina actualizarea PC și înlocuirea cu NOP a instrucțiunilor mai noi ce deja au intrat în pipeline:
assign pc_load = r2_opcode == 4'b1100;
Actualizarea stării procesorului
La execuția instrucțiunii JMP contorul de program, PC, se va încărca cu adresa de salt. Descrierea PC din laboratorul precedent se completează cu o ramură corespunzătoare condiționată de semnalul pc_load :
always @(posedge clk) begin
if(rst)
pc <= 0;
else if(halt)
pc <= pc;
else if(pc_load )
pc <= pc_target;
else
pc <= pc + 1;
end
Adresa de salt este byte-ul inferior al instrucțiunii. Când instrucțiunea de salt ajunge în nivelul de execuție, byte-ul inferior este accesibil ca operand 2:
assign pc_target = r2_operand2;
De asemenea, pentru ca acest byte să ajungă în întregime ca operand 2 în execuție, operandul 2 al instrucțiunii JMP trebuie selectat în etapa READ la fel cum este selectat acesta pentru instrucțiunea LOADC. Codul de selecție se modifică astfel:
assign operand2 = ((r1_opcode == 4'b1000) | (r1_opcode == 4'b1100)) ? r1_instr_data : rdata2; // LOADC sau JMP
La sfârșitul execuției instrucțiunii de salt, instrucțiunile mai noi ce sunt deja în nivelurile inferioare de pipeline se înlocuiesc cu instrucțiuni NOP. Astfel registrul pipeline R1 se va încărca cu instrucțiunea NOP (ce are codul binar 'b0000) dacă semnalul de control pc_load este activ:
always @(posedge clk) begin
if(rst)
r1 <= 0;
else if(halt)
r1 <= r1;
else if(pc_load )
r1 <= 0;
else
r1 <= instr;
end
Analog se completează și descrierea registrului pipeline R2.
Rularea unui program cu instrucțiuni de salt
Pentru testarea calculatorului se modifică programul din memoria de program astfel:
0 LOADC R0 #255
1 LOADC R1 #254
2 LOADC R2 #253
3 JMP #7
4 ADD R6 R5 R4
5 STORE R2 R6
6 HALT
7 LOAD R4 R0
8 LOAD R5 R1
9 JMP #4
Activități suplimentare
Activitatea 1
Implementați o instrucțiune de salt condiționată, JMPZ #target. Saltul se va executa (pc_load va fi activat) numai dacă registrul indicator Z (actualizat după execuția instrucțiunii precedente) este 1. Procesorul pipeline se va completa cu un registru pentru indicatori actualizat pe ceas ce salvează în permanență indicatorii (flags) de la ieșirea ALU. Codul instrucțiunii JMPZ se alege 'b1101.
Activitatea 2
Modificați implementarea detecției și gestiunii salturilor necondiționate, JMP #target, astfel încât saltul în program (încărcarea adresei de salt în PC) să aibă loc la sfârșitul etapei READ. În felul acesta se va pierde o singură instrucțiune și nu două.