Laboratorul 4: Diferență între versiuni

De la WikiLabs
(EXECUTE)
(Procesorul pipeline)
Linia 19: Linia 19:
  
 
[[Fișier: asc_lab4_pipeline.png]]
 
[[Fișier: asc_lab4_pipeline.png]]
 +
 +
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:
 +
 +
<syntaxhighlight lang="Verilog">
 +
module processor (
 +
  // interfața procesorului
 +
);
 +
 +
// declarații semnale interne
 +
// descrierea PC
 +
// descrierea R1
 +
// instanță de modul REGS
 +
// ș.a.m.d.
 +
</syntaxhighlight>
 +
  
 
=== PC ===
 
=== PC ===

Versiunea de la data 7 noiembrie 2019 14:46

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.

Asc lab4 harvard.png

Procesorul pipeline

Procesorul are o structură pipeline cu trei niveluri:

  1. FETCH - citirea instrucțiunii din memoria de program
  2. READ - citirea operanzilor (din registrele sursă)
  3. EXECUTE - execuția operației instrucțiunii/accesul în memoria de date

Asc lab4 procesor.png

Î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.

Asc lab4 pipeline.png

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.


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.

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.

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ă 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 cu excepția inițializării locațiilor de program, programul fiind î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, date în program ca valori imediate în instrucțiunile LOADC (load constant), apoi transferă două numere din ultimele doua locații de memorie în doua registre, R4 si R5. Urmează adunarea celor două numere, rezultatul fiind salvat în registrul R6, rezultat ce apoi este stocat în memorie î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 unui instrucțiuni NOP între oricare două instrucțiuni succesive dacă una dintre ele folosește rezultatul instrucțiunii precedente. 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 care este verificat, interfața lui rămânând aceeași.