Diferență între revizuiri ale paginii „Laboratorul 4”
(Nu s-au afișat 9 versiuni intermediare efectuate de același utilizator) | |||
Linia 130: | Linia 130: | ||
=== EXECUTE === | === EXECUTE === | ||
− | Logica combinațională de execuție include unitatea aritmetico-logică (ALU). Aceasta preia operanzii salvați în registrul pipeline R2 și livrează rezultatul în baza codului instrucțiunii din același registru. Pentru instrucțiunile de acces la memoria de date (''LOAD'' și ''STORE''), operandul 1 este trimis spre aceasta ca adresă de date. Instrucțiunea ''STORE'' trimite și operandul 2, cel ce trebuie salvat în memoria de date, și activează semnalul de control de scriere, '''write'''. Instrucțiunea ''LOAD'' preia data citită din memorie, '''data_in''', și o selectează drept rezultat ce trebuie scris în registrul destinație, selecție ce se face comparând codul instrucțiunii din registrul pipeline R2 (codul instrucțiunii aflate în execuție) cu codul instrucțiunii ''LOAD''. | + | Logica combinațională de execuție include unitatea aritmetico-logică (ALU). Aceasta preia operanzii salvați în registrul pipeline R2 și livrează rezultatul în baza codului instrucțiunii din același registru. Pentru instrucțiunile de acces la memoria de date (''LOAD'' și ''STORE''), operandul 1 este trimis spre aceasta ca adresă de date. Instrucțiunea ''STORE'' trimite și operandul 2, cel ce trebuie salvat în memoria de date, și activează semnalul de control de scriere, '''write'''. Instrucțiunea ''LOAD'' preia data citită din memorie, '''data_in''', și o selectează drept rezultat ce trebuie scris în registrul destinație, selecție ce se face comparând codul instrucțiunii din registrul pipeline R2 (codul instrucțiunii aflate în execuție) cu codul instrucțiunii ''LOAD'' (1001 în binar). |
<syntaxhighlight lang="Verilog"> | <syntaxhighlight lang="Verilog"> | ||
− | assign write | + | assign data_addr = r2_operand1; |
− | assign result = (r2_opcode == 4'b1001) ? data_in : alu_result; | + | assign data_out = // <<<<< COMPLETAȚI CODUL ! |
+ | assign write = // activ numai cand STORE e in faza de executie // <<<<< COMPLETAȚI CODUL ! | ||
+ | assign result = (r2_opcode == 4'b1001) ? data_in : alu_result; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Tot logica combinațională din etapa de execuție generează semnalul de control al scrierii în setul de registre. Instrucțiunile ''NOP'' și ''HALT'' nu trebuie să actualizeze nimic, la fel și instrucțiunea ''STORE''. | + | Tot logica combinațională din etapa de execuție generează și semnalul de control al scrierii în setul de registre. Instrucțiunile ''NOP'' și ''HALT'' nu trebuie să actualizeze nimic, la fel și instrucțiunea ''STORE''. |
<syntaxhighlight lang="Verilog"> | <syntaxhighlight lang="Verilog"> | ||
Linia 160: | Linia 162: | ||
assign halt = // <<<<< COMPLETAȚI CODUL ! | assign halt = // <<<<< COMPLETAȚI CODUL ! | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
== Memoria de date == | == Memoria de date == | ||
Este identică cu memoria din laboratorul precedent, dar fără inițializarea programului, acesta fiind stocat în altă memorie. | Este identică cu memoria din laboratorul precedent, dar fără inițializarea programului, acesta fiind stocat în altă memorie. | ||
+ | |||
== Memoria de program == | == Memoria de program == | ||
Linia 194: | Linia 196: | ||
end | end | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
== Modulul de top (calculatorul) == | == Modulul de top (calculatorul) == | ||
Linia 204: | Linia 207: | ||
Modulul de testare este identic cu modulul de testare folosit în laboratorul precedent. Practic se schimbă doar numele modulului de top care este verificat, interfața lui rămânând aceeași. | Modulul de testare este identic cu modulul de testare folosit în laboratorul precedent. Practic se schimbă doar numele modulului de top care este verificat, interfața lui rămânând aceeași. | ||
+ | |||
+ | |||
+ | == Activități suplimentare == | ||
+ | |||
+ | === Activitatea 1 === | ||
+ | |||
+ | Rearanjați instrucțiunile în memoria de program astfel încât să folosiți o singură instrucțiune ''NOP'' în loc de două. Verificați în simulare că rezultatul execuției programului este cel corect. | ||
+ | |||
+ | |||
+ | === Activitatea 2 === | ||
+ | |||
+ | Implementați instrucțiunea de salt necondiționat ''JMP''. | ||
+ | |||
+ | # Alegeți pentru ''opcode'' o combinație neutilizată (de exemplu 1110) și folosiți câmpul constantă (octetul inferior al instrucțiunii) pentru adresa de salt. | ||
+ | # Modificați logica de selecție a operandului 2 astfel încât constanta să fie selectată atât pentru instrucțiunea ''LOADC'' cât și pentru instrucțiunea ''JMP''. | ||
+ | # Modificați logica de generare a semnalelor de control din faza de execuție astfel ca instrucțiunea ''JMP'' să nu activeze scrierea în memorie și nici actualizarea setului de registre. | ||
+ | # Adăugați logica de generare a semnalului '''load_pc''' în faza de execuție a instrucțiunii ''JMP''. | ||
+ | # Adăugați în descrierea PC ramura condiționată de activarea semnalului '''load_pc''', în care '''pc''' va lua valoarea operandului 2 din registrul pipeline R2. | ||
+ | |||
+ | Verificați corectitudinea implementării instrucțiunii de salt. | ||
+ | |||
+ | # Inserați instrucțiuni de salt în programul folosit în lucrarea de laborator. Pentru că procesorul nu are mecanisme de gestiune a dependențelor de control, orice instrucțiune de salt din program va fi urmată de două instrucțiuni ''NOP''. | ||
+ | |||
+ | <syntaxhighlight lang="Verilog"> | ||
+ | 0 LOADC R0 #255 | ||
+ | 1 LOADC R1 #254 | ||
+ | 2 LOADC R2 #253 | ||
+ | 3 LOAD R4 R0 | ||
+ | 4 LOAD R5 R1 | ||
+ | 5 JMP #9 | ||
+ | 6 NOP | ||
+ | 7 NOP | ||
+ | 8 HALT | ||
+ | 9 ADD R6 R5 R4 | ||
+ | 10 NOP | ||
+ | 11 STORE R2 R6 | ||
+ | 12 JMP #8 | ||
+ | 13 NOP | ||
+ | 14 NOP | ||
+ | </syntaxhighlight> |
Versiunea curentă din 8 noiembrie 2019 09:38
Arhitectura Harvard
Arhitectura de calculator Harvard are magistrale distincte pentru accesul la program și la date, permițând astfel citirea unei instrucțiuni în paralel cu citirea sau scrierea unei date de către o altă instrucțiune. Programul și datele se află în memorii separate sau într-o memorie comună dar cu porturi multiple de acces.
Procesorul pipeline
Procesorul are o structură pipeline cu trei niveluri:
- FETCH - citirea instrucțiunii din memoria de program
- READ - citirea operanzilor (din registrele sursă)
- EXECUTE - execuția operației instrucțiunii/accesul în memoria de date
În această lucrare de laborator se va implementa un procesor pipeline fără gestiunea dependențelor de date sau de control, ce procesează setul de instrucțiuni din laboratoarele precedente. Implementarea va reutiliza integral blocurile ALU și REGS proiectate în laboratorul 1, și cu mici modificări memoria din laboratorul 2.
Schema detaliată a structurii pipeline pune în evidență toate căile de date și semnalele de control. Schema este desenată astfel încât fluxul de instrucțiuni și de date să fie de la stânga la dreapta, cu excepția căii de scriere în setul de registre și a semnalului special de control halt.
Modulul procesor va avea o descriere mixtă, structurală, pentru că instanțiază modulele ALU și REGS, și comportamentală, pentru că toate celelalte componente (contor de program, registre pipeline, multiplexoare, logica de generare de semnale de control) sunt descrise folosind construcții always și assign. Pentru a avea un modul verilog ușor de urmărit și de depanat, este de preferat ca ordinea instanțierilor și a diferitelor construcții always și assign din descriere să corespundă ordonării blocurilor în structura pipeline:
module processor (
// interfața procesorului
);
// declarații semnale interne
// descrierea PC
// descrierea R1
// instanță de modul REGS
// ș.a.m.d.
endmodule
PC
Contorul de program incrementează în fiecare ciclu de ceas cu excepția cazului în care procesorul a fost oprit de instrucțiunea HALT.
always @(posedge clk) begin
if(rst)
pc <= 0;
else if(halt)
pc <= pc;
else
pc <= pc + 1;
end
FETCH
Setul de instrucțiuni fiind foarte simplu pentru acest laborator, nu există logică suplimentară în etapa de citire a instrucțiunii. Procesorul trimite spre memoria de program adresa instrucțiunii, adică valoarea PC, și preia instrucțiunea de la ieșirea acesteia.
assign instr_addr = pc;
Registrul pipeline R1
Acesta salvează la finalul ciclului de ceas (de citire a instrucțiunii) instrucțiunea primită de la memoria de program. Actualizarea registrului pipeline este oprită dacă procesorul execută instrucțiunea HALT. La resetare registrul pipeline este încărcat cu 0, adică cu codul instrucțiunii NOP.
always @(posedge clk) begin
if(rst)
r1 <= 0;
else if(halt)
r1 <= r1;
else
r1 <= instr;
end
READ
În etapa de citire a operanzilor procesorul accesează setul de registre pe baza câmpurilor sursă ale instrucțiunii din registrul R1, preia datele din registrele sursă și livrează operanzii pentru a fi stocați în registrul pipeline următor, R2. Pentru instrucțiunea LOADC operandul 2 este preluat direct din câmpul constantă al instrucțiunii din R1. Logica de selecție a multiplexorului operandului 2 se bazează pe comparația codului instrucțiunii aflate în această fază de procesare, r1_opcode, cu codul instrucțiunii LOADC.
assign operand2 = (r1_opcode == 4'b1000) ? r1_instr_data : rdata2; // LOADC are în binar codul 1000
Instrucțiunea de atribuire condițională de mai sus este descrierea funcțională a multiplexorului de la intrarea registrului R2 și a blocului logic de generare a semnalului de selecție pentru multiplexor.
Pentru claritatea codului și pentru a identifica ușor semnalele pe formele de undă, folosiți nume dedicate pentru fiecare câmp al instrucțiunii din R1.
assign r1_opcode = r1[15:12];
assign r1_dest = r1[11: 8];
assign r1_sursa1 = r1[ 7: 4];
assign r1_sursa2 = // <<<<< COMPLETAȚI CODUL !
assign r1_instr_data = // <<<<< COMPLETAȚI CODUL !
Registrul pipeline R2
Acest registru pipeline salvează operanzii și o parte a instrucțiunii ce a trecut de etapa de citire, câmpurile sursă și constantă nemaifiind necesare. Spre deosebire de registrul R1 care era declarat ca o singură variabilă r1, registrul R2 este compus din patru variabile distincte. Este foarte important ca după execuția instrucțiunii HALT acest registru pipeline să nu se mai actualizeze - astfel instrucțiunea HALT rămâne în registrul pipeline R2 asigurând blocarea procesorului până la resetare. La resetare registrul pipeline este încărcat cu 0, adică cu codul instrucțiunii NOP.
always @(posedge clk) begin
if(rst) begin
r2_opcode <= 0;
r2_dest <= 0;
r2_operand1 <= 0;
r2_operand2 <= 0;
end
else if(halt) begin
r2_opcode <= // <<<<< COMPLETAȚI CODUL !
. . . . .
end
else begin
r2_opcode <= r1_opcode;
r2_dest <= // <<<<< COMPLETAȚI CODUL !
r2_operand1 <= operand1;
r2_operand2 <= // <<<<< COMPLETAȚI CODUL !
end
end
EXECUTE
Logica combinațională de execuție include unitatea aritmetico-logică (ALU). Aceasta preia operanzii salvați în registrul pipeline R2 și livrează rezultatul în baza codului instrucțiunii din același registru. Pentru instrucțiunile de acces la memoria de date (LOAD și STORE), operandul 1 este trimis spre aceasta ca adresă de date. Instrucțiunea STORE trimite și operandul 2, cel ce trebuie salvat în memoria de date, și activează semnalul de control de scriere, write. Instrucțiunea LOAD preia data citită din memorie, data_in, și o selectează drept rezultat ce trebuie scris în registrul destinație, selecție ce se face comparând codul instrucțiunii din registrul pipeline R2 (codul instrucțiunii aflate în execuție) cu codul instrucțiunii LOAD (1001 în binar).
assign data_addr = r2_operand1;
assign data_out = // <<<<< COMPLETAȚI CODUL !
assign write = // activ numai cand STORE e in faza de executie // <<<<< COMPLETAȚI CODUL !
assign result = (r2_opcode == 4'b1001) ? data_in : alu_result;
Tot logica combinațională din etapa de execuție generează și semnalul de control al scrierii în setul de registre. Instrucțiunile NOP și HALT nu trebuie să actualizeze nimic, la fel și instrucțiunea STORE.
always @(*) begin
case(r2_opcode)
4'b0000: regs_wen = 0; // NOP
4'b0001: regs_wen = 1; // ADD
4'b0010: // SUB // <<<<< COMPLETAȚI CODUL !
4'b1000: // LOADC // <<<<< COMPLETAȚI CODUL !
4'b1001: // LOAD // <<<<< COMPLETAȚI CODUL !
4'b1010: // STORE // <<<<< COMPLETAȚI CODUL !
4'b1111: // HALT // <<<<< COMPLETAȚI CODUL !
// urmeaza implementarea altor operatii
default: regs_wen = 0;
endcase
end
Etapa de execuție trebuie să genereze și semnalul de control halt atunci când instrucțiunea curentă din execuție are codul HALT.
assign halt = // <<<<< COMPLETAȚI CODUL !
Memoria de date
Este identică cu memoria din laboratorul precedent, dar fără inițializarea programului, acesta fiind stocat în altă memorie.
Memoria de program
Are nevoie doar de logica de acces pentru citire și blocul de inițializare a programului. Atenție la dimensiunea locațiilor de memorie și a portului de ieșire, instrucțiunea fiind de 16 biți!
Initializare program
Programul este cel din laboratorul prededent. Programul incarcă în primele trei registre trei adrese de memorie, specificate în program ca valori imediate în instrucțiunile LOADC (load constant), apoi transferă două numere din ultimele doua locații ale memoriei de date în doua registre, R4 si R5. Urmează adunarea celor două numere, rezultatul fiind salvat în registrul R6, rezultat ce apoi este stocat în memoria de date în antepenultima ei locație. Ultima instrucțiune oprește definitiv procesorul (din starea halt procesorul nu mai poate fi reactivat decît prin resetare).
Deoarece procesorul implementat nu gestionează dependențele de date este obligatorie introducerea în program a câte unei instrucțiuni NOP între oricare două instrucțiuni succesive care depind una de alta. Instrucțiunea ADD din program folosește operandul încărcat în registru de ultima instrucțiune LOAD, iar instrucțiunea STORE are ca operand rezultatul instrucțiunii ADD. Codul instrucțiunii NOP este 0.
initial begin
memory[00] = 16'b1000_0000_1111_1111; // LOADC R0 #255
memory[01] = 16'b1000_0001_1111_1110; // LOADC R1 #254
// LOADC R2 #253 // <<<<< COMPLETAȚI CODUL !
// LOAD R4 R0 // <<<<< COMPLETAȚI CODUL !
// LOAD R5 R1 // <<<<< COMPLETAȚI CODUL !
// NOP // <<<<< COMPLETAȚI CODUL !
// ADD R6 R5 R4 // <<<<< COMPLETAȚI CODUL !
// NOP // <<<<< COMPLETAȚI CODUL !
// STORE R2 R6 // <<<<< COMPLETAȚI CODUL !
// HALT // <<<<< COMPLETAȚI CODUL !
end
Modulul de top (calculatorul)
Modului de top, a cărui schemă este dată în prima figură, conține trei instanțe, corespunzătoare celor trei blocuri componente ale calculatorului. Singurii pini ai modului de top sunt o intrare de ceas și una de reset.
Testarea calculatorului
Modulul de testare este identic cu modulul de testare folosit în laboratorul precedent. Practic se schimbă doar numele modulului de top care este verificat, interfața lui rămânând aceeași.
Activități suplimentare
Activitatea 1
Rearanjați instrucțiunile în memoria de program astfel încât să folosiți o singură instrucțiune NOP în loc de două. Verificați în simulare că rezultatul execuției programului este cel corect.
Activitatea 2
Implementați instrucțiunea de salt necondiționat JMP.
- Alegeți pentru opcode o combinație neutilizată (de exemplu 1110) și folosiți câmpul constantă (octetul inferior al instrucțiunii) pentru adresa de salt.
- Modificați logica de selecție a operandului 2 astfel încât constanta să fie selectată atât pentru instrucțiunea LOADC cât și pentru instrucțiunea JMP.
- Modificați logica de generare a semnalelor de control din faza de execuție astfel ca instrucțiunea JMP să nu activeze scrierea în memorie și nici actualizarea setului de registre.
- Adăugați logica de generare a semnalului load_pc în faza de execuție a instrucțiunii JMP.
- Adăugați în descrierea PC ramura condiționată de activarea semnalului load_pc, în care pc va lua valoarea operandului 2 din registrul pipeline R2.
Verificați corectitudinea implementării instrucțiunii de salt.
- Inserați instrucțiuni de salt în programul folosit în lucrarea de laborator. Pentru că procesorul nu are mecanisme de gestiune a dependențelor de control, orice instrucțiune de salt din program va fi urmată de două instrucțiuni NOP.
0 LOADC R0 #255
1 LOADC R1 #254
2 LOADC R2 #253
3 LOAD R4 R0
4 LOAD R5 R1
5 JMP #9
6 NOP
7 NOP
8 HALT
9 ADD R6 R5 R4
10 NOP
11 STORE R2 R6
12 JMP #8
13 NOP
14 NOP