Table of Contents

Laboratorul 8 - Proiectul SOC-1

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

Module

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:

Semnalul de ceas

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

Cea mai simplă metodă de a opri un computer din a mai executa instrucțiuni este oprirea ceasului acestuia.

Registre

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:

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

Program Counter (PC)

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:

always @(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

Instruction Register (IR)

Î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

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

Memorie

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

Controller

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

UAL

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.

Flags

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

Descriere extinsa flag-uri

Descriere extinsa flag-uri

  • Z (Zero) - indică dacă rezultatul unei operații aritmetice este zero.
  • C (Carry) - indică faptul că s-a realizat un transport la nivelul bitului cel mai semnificativ in cazul unei operații aritmetice. Altfel spus, a avut loc o depășire în aritmetica modulo N considerată.

Explicație: În procesorul nostru pe 8 biți, 255 + 1, deși ar trebui să aibă rezultatul 256, de fapt acesta este 0 din cauza aritmeticii modulo 256 (28). Pentru a diferenția dintre un 0 apărut real și unul cauzat de o depășire, se utilizează acest semnal de carry.

  • V (Overflow) - arată că, în cazul unei operații aritmetice cu semn, complementul față de doi al rezultatului nu ar încăpea în numărul de biți folosiți pentru a reprezenta operanzii.

Explicație: Cu alte cuvinte, se poate întampla ca adunând două numere pozitive, să obținem unul negativ (127signed + 1signed = -128signed), dar și adunând două numere negative să obținem unul pozitiv (-128signed + (-1)signed = +127signed). Evident, rezultatul nu este corect în aceste situații, și semnalarea se face prin flag-ul de overflow. Dacă am aduna un număr pozitiv (MSB = 0) cu unul negativ (MSB = 1), atunci nu obțin overflow.

  • N (Negative) - semnul rezultatului unei operații aritmetice (este pur și simplu valoarea bitului de semn al rezultatului)
  • S (Sign) - este un flag unic AVR, calculat după formula S = N xor V, ce reprezintă “care ar fi trebuit să fie semnul corect al rezultatului”.

Explicație: Cu alte cuvinte, dacă N == 1, dar și V == 1, înseamnă că rezultatul este negativ, dar din cauza unei depășiri S este setat în acest caz pe 0, semnalând că semnul “corect” al operației ar fi trebuit să fie pozitiv.

TL;DR

Arhitectura include următoarele module:

Clock:

Generează semnalul de ceas pentru sincronizarea tuturor modulelor. Se oprește la primirea semnalului hlt.

Controller / Unitatea de Control:

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.

Registre

Trei registre (A, B, C) pe 8 biți – suportă reset, load, inc, dec. Utilizate pentru stocarea temporară a datelor.

Program Counter (PC)

Păstrează adresa următoarei instrucțiuni.

Instruction Register (IR)

Încarcă instrucțiunea curentă adusă din memorie.

Magistrala (Bus)

Canal comun de date între module.

Memorie

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.

ALU (UAL)

Realizează 8 operații: ADD, SUB, AND, OR, XOR, CMA, RAL, RAR.

Flags

Reține rezultatul logic al ultimei operații:

Exerciții de laborator

  1. (5p) Implementați un adder carry-look ahead, astfel: urmăriți codul din ALU, ce se instanțiază și completați modulul corespunzător (cla_adder), conform cunoștințelor de la labul precedent.
  2. (5p) Implementați flagul de Carry folosind indicatiile din modulul de flags, dar si logica prezentata in descrierea flag-urilor.
  3. (Bonus) Implementați flagul de Overflow asemeni celui de Carry de la task-ul anterior.

Resurse

Scheletul de laborator