Laboratorul 1: Diferență între versiuni

De la WikiLabs
(Nu s-au afișat 19 versiuni intermediare efectuate de același utilizator)
Linia 1: Linia 1:
== Arhitectura von Neumann ==
 
 
Arhitectura de calculator von Neumann este caracterizată printr-o memorie unică, în care se află atât programul cât și datele, și o magistrală unică, folosită pentru transferul instrucțiunilor de la memorie la procesor, a datelor între procesor și memorie sau între procesor și oricare din dispozitivele de intrare/ieșire conectate la magistrală.
 
 
În această lucrare de laborator se va realiza un calculator simplu, având doar procesorul și memoria.
 
 
[[Fișier: asc_lab1_neumann.png]]
 
 
Având o singură cale de acces atât pentru instrucțiuni, cât și pentru date, arhitectura von Neumann procesează o instrucțiune în mai mulți pași, în fiecare pas magistrala comună fiind configurată pentru un anumit transfer între blocurile și registrele conectate la aceasta. Secvențierea procesării instrucțiunilor se face cu ajutorul unității de control al procesorului (UCP), aceasta fiind implementată fie ca o mașină de stări, fie ca o structură microprogramabilă.
 
 
Pentru a menține simplitatea la un nivel adecvat unui laborator introductiv, se va considera o arhitectură de procesor cu acumulator, fără set de registre, ale cărui instrucțiuni folosesc cel mult un operand din memorie, prin adresare imediată sau adresare directă.
 
 
Schema bloc a procesorului conține o unitate aritmetico-logică (ALU), un contor de program (PC) ce se poate incrementa sau încărca cu o valoare dată, un registru pentru adresarea memoriei (ADDR), un registru pentru operandul adus din memorie (DATAIN), registrul acumulator (ACC), câțiva biți pentru indicații despre rezultatul ultimei operații efectuate de ALU (F) și unitatea de control a procesorului (UCP). Pentru a nu complica desenul, în schema bloc au fost schițate doar traseele principale (magistrala de date, calea de adresare):
 
 
[[Fișier: asc_lab1_microneumann.png]]
 
 
 
=== PC ===
 
 
Contorul de program este folosit pentru citirea instrucțiunilor din memorie. Acesta poate fi incrementat sau încărcat cu valoarea de pe magistrala de date, dar numai la comanda UCP.
 
 
<syntaxhighlight lang="Verilog">
 
always @(poosedge clk) begin
 
    if(rst)
 
        pc <= 0;
 
    else begin
 
        if(pc_load)
 
            // pc ia valoarea de pe magistrala comună de date
 
        else if (pc_incr)
 
            pc <= pc + 1;
 
        else
 
            pc <= pc;
 
    end
 
end
 
</syntaxhighlight>
 
 
 
=== ADDR ===
 
 
Registrul de adresare a memoriei pentru citirea datelor este un registru elementar ce poate fi doar încărcat:
 
 
<syntaxhighlight lang="Verilog">
 
always @(poosedge clk) begin
 
    if(rst)
 
        raddr <= 0;
 
    else begin
 
        if(raddr_load)
 
            // ADDR ia valoarea de pe magistrala comună de date
 
        else
 
            raddr <= raddr;
 
    end
 
end
 
</syntaxhighlight>
 
 
 
=== ALU ===
 
=== ALU ===
 
Unitatea aritmetico-logică, ALU, este responsabilă de execuţia instrucţiunilor.
 
Unitatea aritmetico-logică, ALU, este responsabilă de execuţia instrucţiunilor.
Linia 64: Linia 10:
 
always @(*) begin
 
always @(*) begin
 
     case(opcode)
 
     case(opcode)
     `ADD: rezultat = operand1 + operand2;
+
     4'b0001: rezultat = operand1 + operand2;
     `SUB: rezultat = operand1 - operand2;
+
     4'b0010: rezultat = operand1 - operand2;
 
     // urmeaza implementarea altor operatii
 
     // urmeaza implementarea altor operatii
 
     endcase
 
     endcase
Linia 84: Linia 30:
 
|-
 
|-
 
| <code>AND</code> || ȘI logic || fiecare bit al rezultatului este ȘI logic între biții corespunzători ai operanzilor
 
| <code>AND</code> || ȘI logic || fiecare bit al rezultatului este ȘI logic între biții corespunzători ai operanzilor
 +
|-
 +
| <code>OR</code> || SAU logic || fiecare bit al rezultatului este SAU logic între biții corespunzători ai operanzilor
 +
|-
 +
| <code>XOR</code> || XOR logic || fiecare bit al rezultatului este SAU-EXCLUSIV logic între biții corespunzători ai operanzilor
 
|-
 
|-
 
| <code>CMP</code> || comparație  ||  Z și N se modifică conform tabelului 2
 
| <code>CMP</code> || comparație  ||  Z și N se modifică conform tabelului 2
 +
|-
 +
| <code>LOAD</code> || transfer||  operandul2 este transferat la ieșirea ALU
 +
|-
 +
| <code>STORE</code> || transfer||  operandul2 este transferat la ieșirea ALU
 
|}
 
|}
 +
 +
Tabelul 1 nu conține toate instrucțiunile setului de instrucțiuni, ci numai instrucțiunile care folosesc ALU. Pentru instrucțiunile ce nu folosesc ALU implementarea execuției lor în ALU poate fi ignorată, dar rezultatul de la ieșirea ALU nu trebuie scris în vreun registru.
  
 
Rezultatul operației de comparație este semnalizat prin biții indicatori ai rezultatului:
 
Rezultatul operației de comparație este semnalizat prin biții indicatori ai rezultatului:
Linia 100: Linia 56:
 
| operand1 < operand2 || 0 || 1
 
| operand1 < operand2 || 0 || 1
 
|}
 
|}
 +
 +
Biții indicatori sunt calculați pentru fiecare operație și reflectă starea rezultatului. Procesorul implementat în laborator are doi indicatori, '''Z''' și '''N'''. Dacă rezultatul este zero se activează bitul '''Z''' (zero). Dacă rezultatul este negativ se activează ieșirea '''N''' (negativ). Operația de comparație poate fi implementată ca o operație de scădere fără destinație (se salvează în procesor doar biții indicatori nu și rezultatul):
 +
 +
<syntaxhighlight lang="Verilog">
 +
assign Z = (result == 0);
 +
assign N = result[7]; // în complement față de 2 bitul MSB este bit de semn
 +
</syntaxhighlight>
 +
 +
=== REGS ===
 +
 +
Setul de registre are 16 registre de 8 biți. Fiecare registru poate fi sursa oricărui operand și poate fi destinație.
 +
Setul de registre poate avea porturi distincte pentru scriere și pentru citire, precum și porturi separate pentru fiecare operand citit.
 +
Implementarea aleasă pentru acest laborator are un set de registre cu trei porturi, un port pentru citirea primului operand ('''raddr1''', '''rdata1'''), altul pentru citirea celui de al doilea operand ('''raddr2''', '''rdata2''') și un port pentru scriere ('''waddr''', '''wdata'''). Portul de scriere folosește un semnal de control, '''wen''', ce activează scrierea numai pentru anumite instrucțiuni. Pentru accesul la un registru este nevoie de o adresă de 4 biți. Semnalele de adresă '''raddr1''', '''raddr2''' și '''waddr''' sunt de 4 biți. Datele sunt pe 8 biți, prin urmare registrele, ieșirile de date '''rdata1''' și '''rdata2''', dar și intrarea de date '''wdata''' sunt fiecare de câte 8 biți.
 +
 +
[[Fișier: asc_lab1_regs.png]]
 +
 +
Scrierea în setul de registre este secvențială, pe frontul semnalului de ceas de la intrarea '''clk''' (nereprezentată în figura de mai sus).
 +
 +
<syntaxhighlight lang="Verilog">
 +
reg [7:0] registru [0:15]; // set de 16 registre a câte 8 biți fiecare
 +
 +
// portul de scriere
 +
always @(posedge clk) begin
 +
    if(wen) registru[waddr] <= wdata; // scriere sincronă - pe ceas - a rezultatului în registrul destinație
 +
end
 +
 +
// portul 1 de citire
 +
assign rdata1 = registru[raddr1]; // ieșirea rdata1 este valoarea din registrul cu numărul raddr1
 +
// portul 2 de citire
 +
                                  // <- completați codul
 +
</syntaxhighlight>
 +
 +
=== RALU ===
 +
 +
Unitatea aritmetico-logică împreună cu setul de registre formează RALU (Register and ALU).
 +
 +
[[Fișier: asc_lab1_ralu.png]]
 +
 +
=== validare RALU ===
 +
 +
Scrieti un modul de testare ce instanțiază setul de registre și ALU, generează ceasul, semnalul de reset și o secvență de instrucțiuni.
 +
 +
==== generarea ceasului ====
 +
 +
<syntaxhighlight lang="Verilog">
 +
initial begin
 +
    clk = 0;                // initialization at time 0
 +
    forever #10 clk = ~clk;  // toggle the clock at each 10 simulation steps
 +
end
 +
</syntaxhighlight>
 +
 +
==== generarea semnalului de reset ====
 +
 +
<syntaxhighlight lang="Verilog">
 +
initial begin
 +
    rst = 0;
 +
    #13 rst = 1; // reset activ în 1 logic
 +
    #20 rst = 0;
 +
end
 +
</syntaxhighlight>
 +
 +
Întîrzierile sunt alese astfel încât fronturile semnalului de reset să nu coincidă cu fronturile ceasului.
 +
 +
==== generarea secvenței de instrucțiuni ====
 +
 +
La fiecare ceas se setează valorile intrărilor '''opcode''', '''sursa1''', '''sursa2''', '''dest''' și '''wen''' cu valori care să corespundă instrucțiunii dorite.
 +
Pentru a ne asigura că toate semnalele se modifică numai după frontul activ al ceasului, vom aștepta de fiecare dată acest front folosind instrucțiunea <syntaxhighlight lang="Verilog" inline>@(posedge clk);</syntaxhighlight>
 +
Semnalul '''wen''' se setează la '''1''' sau la '''0''' după cum dorim ca rezultatul să se salveze sau nu în registrul destinație.
 +
Semnalul '''wen''' nu face parte din instrucțiune. În laboratorul 2 el va fi generat de unitatea de control a procesorului (UCP) în funcție de codul instrucțiunii. De exemplu instrucțiunea CMP nu modifică niciun registru ci doar biții indicatori, prin urmare '''wen''' va fi în permanență '''0''' pe durata procesării acestei instrucțiuni.
 +
 +
<syntaxhighlight lang="Verilog">
 +
initial begin
 +
    opcode = 4'b0000; dest = 4'd0; sursa1 = 4'd0; sursa2 = 4'd0; wen = 1'b0;
 +
    #26 // se așteaptă finalizarea resetului
 +
    @(posedge clk);
 +
    opcode = 4'b0001; dest = 4'd7; sursa1 = 4'd6; sursa2 = 4'd5; wen = 1'b1; // ADD R7 R6 R5 // R7 <- R6 + R5
 +
    @(posedge clk);
 +
    opcode = 4'b0010; dest = 4'd7; sursa1 = 4'd7; sursa2 = 4'd4; wen = 1'b1; // SUB R7 R7 R4 // R7 <- R7 - R4
 +
    // alte instrucțiuni
 +
end
 +
</syntaxhighlight>
 +
 +
=== initializarea valorilor in registre ===
 +
 +
Se poate face a) la reset, dacă modulul are intrare de reset, sau b)  într-un bloc '''initial''' folosit doar pentru simulare.
 +
 +
a)
 +
 +
<syntaxhighlight lang="Verilog">
 +
always @(posedge clk) begin
 +
    if(rst) begin
 +
        registru[0] = 13;
 +
        . . .
 +
    end
 +
    else begin
 +
        if(wen) . . .
 +
    end
 +
end
 +
</syntaxhighlight>
 +
 +
b)
 +
 +
<syntaxhighlight lang="Verilog">
 +
initial begin
 +
    registru[0] = 13;
 +
    registru[1] = 27;
 +
    . . . .
 +
end
 +
</syntaxhighlight>
 +
 +
Dacă blocul '''initial''' este scris în modulul de testare iar variabilele inițializate sunt din interiorul modului testat sau chiar dintr-un submodul al acestuia, numele acestora trebuie să fie complet, corespunzător ierarhiei.
 +
 +
<syntaxhighlight lang="Verilog">
 +
    ralu.regs.registru[0] = 13;
 +
</syntaxhighlight>

Versiunea de la data 25 octombrie 2021 09:28

ALU

Unitatea aritmetico-logică, ALU, este responsabilă de execuţia instrucţiunilor. În primul rând, după cum ne şi sugerează numele, instrucţiunile aritmetice şi cele logice, dar şi celelalte categorii de instrucţiuni, în execuţia cărora intervin operaţii precum calculul unor adrese pentru instrucţiunile de acces la memorie sau pentru instrucţiunile de salt relativ. Concret, ALU primeşte operanzii unei instrucţiuni şi oferă rezultatul operaţiei specificate în codul instrucţiunii. De exemplu instrucţiunea de adunare determină în ALU adunarea celor doi operanzi de la intrare, suma acestora (rezultatul) apărând la ieşirea ALU. Un alt exemplu, instrucţiunea de salt relativ determină în ALU adunarea a două numere, valoarea contorului de program (PC) şi valoarea saltului relativ specificată în corpul instrucţiunii, rezultatul fiind noua valoare de contor de program, ce va fi încărcată în contorul de program.

Complexitatea ALU este determinată în primul rând de complexitatea operaţiilor aritmetice ale instrucţiunilor din setul de instrucţiuni, însă depinde, uneori semnificativ, şi de performanţele avute în vedere (viteză, paralelism, consum redus) ce pot modifica radical structura aleasă. La nivel funcţional, fără a fi preocupaţi de implementarea efectivă, ALU poate fi privit ca un bloc multifuncţional a cărui funcţie este selectată de codul operaţiei instrucţiunii:

always @(*) begin
    case(opcode)
    4'b0001: rezultat = operand1 + operand2;
    4'b0010: rezultat = operand1 - operand2;
    // urmeaza implementarea altor operatii
    endcase
end

Descrieți funcțional (comportamental) un ALU de 8 biți ce execută operațiile din tabelul 1.

Asc lab1 alu.png

Tabelul 1
opcode operație detalii
ADD adunare
SUB scădere operandul 2 se scade din operandul 1
AND ȘI logic fiecare bit al rezultatului este ȘI logic între biții corespunzători ai operanzilor
OR SAU logic fiecare bit al rezultatului este SAU logic între biții corespunzători ai operanzilor
XOR XOR logic fiecare bit al rezultatului este SAU-EXCLUSIV logic între biții corespunzători ai operanzilor
CMP comparație Z și N se modifică conform tabelului 2
LOAD transfer operandul2 este transferat la ieșirea ALU
STORE transfer operandul2 este transferat la ieșirea ALU

Tabelul 1 nu conține toate instrucțiunile setului de instrucțiuni, ci numai instrucțiunile care folosesc ALU. Pentru instrucțiunile ce nu folosesc ALU implementarea execuției lor în ALU poate fi ignorată, dar rezultatul de la ieșirea ALU nu trebuie scris în vreun registru.

Rezultatul operației de comparație este semnalizat prin biții indicatori ai rezultatului:

Tabelul 2
Z N
operand1 > operand2 0 0
operand1 = operand2 1 0
operand1 < operand2 0 1

Biții indicatori sunt calculați pentru fiecare operație și reflectă starea rezultatului. Procesorul implementat în laborator are doi indicatori, Z și N. Dacă rezultatul este zero se activează bitul Z (zero). Dacă rezultatul este negativ se activează ieșirea N (negativ). Operația de comparație poate fi implementată ca o operație de scădere fără destinație (se salvează în procesor doar biții indicatori nu și rezultatul):

assign Z = (result == 0);
assign N = result[7]; // în complement față de 2 bitul MSB este bit de semn

REGS

Setul de registre are 16 registre de 8 biți. Fiecare registru poate fi sursa oricărui operand și poate fi destinație. Setul de registre poate avea porturi distincte pentru scriere și pentru citire, precum și porturi separate pentru fiecare operand citit. Implementarea aleasă pentru acest laborator are un set de registre cu trei porturi, un port pentru citirea primului operand (raddr1, rdata1), altul pentru citirea celui de al doilea operand (raddr2, rdata2) și un port pentru scriere (waddr, wdata). Portul de scriere folosește un semnal de control, wen, ce activează scrierea numai pentru anumite instrucțiuni. Pentru accesul la un registru este nevoie de o adresă de 4 biți. Semnalele de adresă raddr1, raddr2 și waddr sunt de 4 biți. Datele sunt pe 8 biți, prin urmare registrele, ieșirile de date rdata1 și rdata2, dar și intrarea de date wdata sunt fiecare de câte 8 biți.

Asc lab1 regs.png

Scrierea în setul de registre este secvențială, pe frontul semnalului de ceas de la intrarea clk (nereprezentată în figura de mai sus).

reg [7:0] registru [0:15]; // set de 16 registre a câte 8 biți fiecare

// portul de scriere
always @(posedge clk) begin
    if(wen) registru[waddr] <= wdata; // scriere sincronă - pe ceas - a rezultatului în registrul destinație
end

// portul 1 de citire
assign rdata1 = registru[raddr1]; // ieșirea rdata1 este valoarea din registrul cu numărul raddr1
// portul 2 de citire
                                  // <- completați codul

RALU

Unitatea aritmetico-logică împreună cu setul de registre formează RALU (Register and ALU).

Asc lab1 ralu.png

validare RALU

Scrieti un modul de testare ce instanțiază setul de registre și ALU, generează ceasul, semnalul de reset și o secvență de instrucțiuni.

generarea ceasului

initial begin
    clk = 0;                 // initialization at time 0
    forever #10 clk = ~clk;  // toggle the clock at each 10 simulation steps
end

generarea semnalului de reset

initial begin
    rst = 0;
    #13 rst = 1; // reset activ în 1 logic
    #20 rst = 0;
end

Întîrzierile sunt alese astfel încât fronturile semnalului de reset să nu coincidă cu fronturile ceasului.

generarea secvenței de instrucțiuni

La fiecare ceas se setează valorile intrărilor opcode, sursa1, sursa2, dest și wen cu valori care să corespundă instrucțiunii dorite. Pentru a ne asigura că toate semnalele se modifică numai după frontul activ al ceasului, vom aștepta de fiecare dată acest front folosind instrucțiunea @(posedge clk); Semnalul wen se setează la 1 sau la 0 după cum dorim ca rezultatul să se salveze sau nu în registrul destinație. Semnalul wen nu face parte din instrucțiune. În laboratorul 2 el va fi generat de unitatea de control a procesorului (UCP) în funcție de codul instrucțiunii. De exemplu instrucțiunea CMP nu modifică niciun registru ci doar biții indicatori, prin urmare wen va fi în permanență 0 pe durata procesării acestei instrucțiuni.

initial begin
    opcode = 4'b0000; dest = 4'd0; sursa1 = 4'd0; sursa2 = 4'd0; wen = 1'b0;
    #26 // se așteaptă finalizarea resetului
    @(posedge clk);
    opcode = 4'b0001; dest = 4'd7; sursa1 = 4'd6; sursa2 = 4'd5; wen = 1'b1; // ADD R7 R6 R5 // R7 <- R6 + R5
    @(posedge clk);
    opcode = 4'b0010; dest = 4'd7; sursa1 = 4'd7; sursa2 = 4'd4; wen = 1'b1; // SUB R7 R7 R4 // R7 <- R7 - R4
    // alte instrucțiuni
end

initializarea valorilor in registre

Se poate face a) la reset, dacă modulul are intrare de reset, sau b) într-un bloc initial folosit doar pentru simulare.

a)

always @(posedge clk) begin
    if(rst) begin
        registru[0] = 13;
        . . . 
    end
    else begin
        if(wen) . . .
    end
end

b)

initial begin
    registru[0] = 13;
    registru[1] = 27;
    . . . .
end

Dacă blocul initial este scris în modulul de testare iar variabilele inițializate sunt din interiorul modului testat sau chiar dintr-un submodul al acestuia, numele acestora trebuie să fie complet, corespunzător ierarhiei.

    ralu.regs.registru[0] = 13;