În acest laborator vom pune în aplicare cunoștințele despre calculatorul învățat la curs - SAP-1 - și vom implementa propriul nostru sistem de calcul pe care îl vom denumi SOC-1.
Scheletul de laborator cuprinde următoarele module:
Pentru a vedea cum se unesc aceste module și cum interacționează între ele, avem următorul schematic:
Toate modulele din modelul nostru de calculator sunt dependente de semnalul de ceas. Din acest motiv, am implementat un modul cât de simplist se poate pentru a simula acest semnal.
module clock( input hlt, input clk_in, output clk_out); assign clk_out = (hlt) ? 1'b0 : clk_in; endmodule
De regulă, acest modul va trimite spre ieșire semnalul de ceas de intrare, cu excepția momentului primirii unui semnal de halt (oprire a execuției programului - se setează la 0).
Registrele sunt o metodă simplă, scumpă și foarte rapidă de a stoca date de mare importanță cu care procesorul urmează să lucreze sau cu care abia a lucrat.
SOC-1 va folosi 3 registre identice, denumite simplu: A, B, C. Acestea vor fi reprezentate pe 8 biți (foarte mici comparativ cu registrele din calculatoarele moderne care au 32 sau 64 de biți, dar suficient pentru procesorul nostru), și în funcție de valorile semnalelor de intrare vor suporta operațiile de:
inc
dec
always @(posedge clk, posedge rst) begin if (rst) begin data <= 8'b0; end else if (load) begin data <= bus[7:0]; end else if (inc) begin data <= data + 1; end else if (dec) begin data <= data - 1; end end
Pentru a executa instrucțiunile (o serie de biți care codifică o anumită acțiune) stocate în memoria ROM, procesorul SOC-1 utilizează Program Counter-ul (PC), o componentă esențială care reține adresa următoarei instrucțiuni ce urmează a fi executată. Instrucțiunile sunt stocate secvențial, începând de la adresa 0, fiecare instrucțiune fiind codificată într-un singur octet.
PC-ul asigură parcurgerea acestor instrucțiuni în ordine, incrementând automat adresa la fiecare front de ceas pozitiv, atunci când semnalul inc este activ. În funcție de semnalele de control (inc, rst și load), comportamentul PC-ului este următorul:
inc
este activ, valoarea PC-ului se incrementează cu 1rst
este activ, PC-ul se resetează la 0load
este activ, PC-ul încarcă o valoare de adresă de pe magistrala de datealways @(posedge clk, posedge rst) begin if (rst) begin pc <= 16'b0; end else if (load) begin pc <= bus; end else if (inc) begin pc <= pc + 1; end end
În timp ce PC-ul se ocupă de stocarea adresei următoarei instrucțiuni, IR-ul încarcă din memorie următoarea instrucțiune de executat sau o resetează în cazul unui semnal de rst.
Magistrala este modul în care modulele își transmit date, respectiv instrucțiuni între acestea. În timp ce un modul pune date pe magistrală, cel căruia îi sunt dedicate datele începe să citească de pe aceasta. Magistrala implementată în proiectul SOC-1 fiind implementat pe FPGA folosește niște semnale pentru a alege modulul de pe care citește datele. Spre exemplu, magistrala va primi semnalul de instruction register enable (ir_en) pentru a citi date de la output-ul ir-ului (ir_out).
always @(*) begin // [...] end else if (pc_en) begin bus = pc_out; // [...] end else begin bus = 16'b0; end end
Memoria modelului nostru de sistem de calcul este de 64K (65536 bytes sau 524288 bits), având adrese pe 16 biți într-un spațiu de adrese ce se extinde de la 0x0000 până la 0xFFFF.
În comportamentul acestui modul puteți observa două registre importante: MAR (Memory Address Register) și MDR (Memory Data Register), ce vor reține adresa din memorie de la care citim/scriem, respectiv datele pe care le scriem în memorie sau citim din memorie.
Un ultim aspect important este faptul că memoria permite apelul de funcții - dar nu și recursivitate - datorită ultimelor două adrese din memorie 0xFFFF si 0xFFFE ce pot memora valoarea PC-ului la intrarea într-o funcție (call) și restituirea acestuia la întoarcerea dintr-un astfel de apel (ret).
always @(posedge clk) begin if (mar_loadh) begin mar[15:8] <= bus[15:8]; end if (mar_loadl) begin mar[7:0] <= bus[7:0]; end if (mdr_load) begin mdr[7:0] <= bus[7:0]; end if (ret) begin mdr[15:8] <= ram[16'hFFFE]; mdr[7:0] <= ram[16'hFFFF]; end else if (call) begin ram[16'hFFFE] <= bus[15:8]; ram[16'hFFFF] <= bus[7:0]; end else if (ram_enh) begin mdr[15:8] <= ram[mar]; end else if (ram_enl) begin mdr[7:0] <= ram[mar]; end else if (ram_load) begin ram[mar] <= mdr; end end
Unitatea de control (UC): decodifică instrucțiunea curentă și pe baza acesteia și a stării interne, generează semnale de control pentru toate resursele procesorului (e.g. registrele de intrare/ieșire și operația aritmetică necesare unei instrucțiuni) și coordonează activarea acestor resurse.
În SOC-1, fiecare instrucțiune este reprezentată pe 8 biți, iar execuția acesteia se desfășoară în mai multe etape.
Etapele executării unei instrucțiuni Orice procesor poate avea un ciclu de prelucrare a instrucțiunilor diferit, în funcție de ISA-ul pe care îl implementează, însă toate vor urma următoarea structură:
Vom studia mai multe despre acestea în Laboratorul 9.
Adresa în ROM este formată prin concatenarea codului instrucțiunii (8 biți) cu etapa curentă a execuției (4 biți), rezultând o adresă pe 12 biți. Conținutul de la acea adresă este un cuvânt de control, care indică exact ce semnale trebuie activate pentru instrucțiunea respectivă, în etapa respectivă.
Unitatea Aritmetică Logică (UAL sau ALU în engleză) este modulul ce definește potențialul unui sistem de calcul. Calculatorul SAP-1 despre care ați învățat la curs era capabil să efectueze operații de adunare și scădere. Spre deosebire de acesta, SOC-1 este capabil să efectueze 8 tipuri de operații diferite:
De menționat este faptul că shiftările se vor face aritmetic, nu logic. Spre exemplu pentru un shift la stânga, dacă ultimul bit înainte de shift era 1 acesta nu va fi pierdut, ci va deveni primul bit. Semnalul de op codifică operația pe care calculatorul trebuie să o efectueze, operații pe care voi va trebui să le implementați.
De asemenea, în cele două blocuri always se poate observa faptul că registrul intern al UAL-ului este updatat sincron cu frontul pozitiv al semnalului de ceas, în timp ce operațiile efectuate de acesta sunt executate asincron.
Ultimul modul necesar pentru ca sistemul de calcul făcut să funcționeze normal este registrul de flag-uri, un registru special pentru a memora diverse caracteristici ale operației anterioare. Un astfel de exemplu este Zero Flag, un bit ce este setat pe 1 în registrul nostru în momentul în care rezultatul ultimei operații efectuate a fost 0.
Proiectul SOC-1 are doar două astfel de flag-uri la momentul curent: Zero Flag și Sign Flag, ale căror valori sunt setate la finalul fiecărui ciclu de ceas (deci pe frontul negativ al semnalului de ceas). Modul în care sunt setate poate fi examinat în secvența de cod de mai jos:
reg[2:0] flags; always @(negedge clk, posedge rst) begin if (rst) begin flags <= 2'b0; end else if (load_a) begin flags[FLAG_Z] <= (a == 0) ? 1'b1 : 1'b0; flags[FLAG_S] <= (a[7] == 1) ? 1'b1 : 1'b0; end else if (load_b) begin flags[FLAG_Z] <= (b == 0) ? 1'b1 : 1'b0; flags[FLAG_S] <= (b[7] == 1) ? 1'b1 : 1'b0; end else if (load_c) begin flags[FLAG_Z] <= (c == 0) ? 1'b1 : 1'b0; flags[FLAG_S] <= (c[7] == 1) ? 1'b1 : 1'b0; end end
Arhitectura include următoarele module:
Generează semnalul de ceas pentru sincronizarea tuturor modulelor. Se oprește la primirea semnalului hlt.
Decodifică instrucțiunea curentă și generează semnalele de control necesare execuției. Folosește o memorie ROM cu adrese pe 12 biți (8 pentru instrucțiune, 4 pentru etapă) → returnează cuvântul de control.
Trei registre (A, B, C) pe 8 biți – suportă reset, load, inc, dec. Utilizate pentru stocarea temporară a datelor.
Păstrează adresa următoarei instrucțiuni.
Încarcă instrucțiunea curentă adusă din memorie.
Canal comun de date între module.
Spațiu de 64K adrese (16 biți) – accese prin registrele MAR/MDR. Suportă apeluri de funcții (call, ret) cu salvarea/restaurarea PC-ului din adresele 0xFFFE și 0xFFFF.
Realizează 8 operații: ADD, SUB, AND, OR, XOR, CMA, RAL, RAR.
Reține rezultatul logic al ultimei operații: