This shows you the differences between two versions of the page.
soc:laboratoare:09 [2024/05/09 10:04] stefan.jumarea [Resurse] |
soc:laboratoare:09 [2025/05/27 17:37] (current) stefan.maruntis [Semnale de control] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 09 - Pipelining ===== | + | ===== Laboratorul 9 - Proiectul SOC-1 - Continuare ===== |
- | ==== Obiective ==== | + | În acest laborator vom continua dezvoltarea calculatorului nostru, SOC-1, și vom detalia unitatea de control, etapele de execuție a instrucțiunilor, instrucțiunile pe care le suportă calculatorul nostru și cum putem scrie cod pe care să îl execute. |
- | În acest laborator vom învăța cum funcționează un pipeline pentru instrucțiuni și vom implementa un pipeline simplu. | + | ==== Stagiile de execuție a unei instrucțiuni ==== |
- | ==== 1. Fazele execuției unei instrucțiuni ==== | + | Execuția instrucțiunilor este de regulă - mai ales în arhitecturile RISC - împărțită în 5 etape: |
+ | * Instruction Fetch (IF) - In aceasta etapa, instructiunea este adusa din memoria sistemului de calcul in Instruction Register (IR) pentru a fi procesata. | ||
+ | * Instruction Decode (ID) - In aceasta etapa, unitatea de control decodifica instructiunea aflata in Instruction Register, transmitand apoi semnalele de control corespunzatoare catre unitatile implicate in executie. | ||
+ | * Execute (EX) - In aceasta etapa, unitatile implicate executa instructiunea, conform semnalelor de control primite anterior. | ||
+ | * Memory (MEM) - Aceasta etapa are loc doar in momentul in care este instructiunea are nevoie de date aflate in memorie. Spre exemplu, in cazul unei simple inlantuiri de instructiuni precum: | ||
+ | * x = 5 | ||
+ | * y = x + 3 | ||
+ | * În urma primei instrucțiuni se va crea o zona în memorie în care se stochează valoarea variabilei x. Pentru a executa a doua instrucțiune, vom avea nevoie de valoarea lui x, stocată anterior în memorie. | ||
+ | * Write-back (WB) - Aceasta este o altă etapă opțională, care are loc doar atunci când dorim să scriem sau să actualizăm datele deja existente în registre. În cazul exemplului anterior, vom dori să salvăm valorile pe care le-am determinat în urmă instrucțiunii. | ||
- | Execuția unei singure instrucțiuni poate fi împărțită în mai multe etape. Acestea pot fi oricât de multe sau puține și pot avea diverse funcții. O împărțire comună, utilizată de primele procesoare RISC, are 5 stagii: | + | ==== Unitatea de control ==== |
- | * **Instruction fetch** (IF): Instrucțiunea este adusă din memorie într-un registru de procesor. | + | |
- | * **Instruction decode** (ID): Instrucțiunea adusă anterior este decodificată și sunt determinate unitățile și datele necesare execuției. | + | |
- | * **Execute** (EX): Acțiunea determinată anterior este executată. | + | |
- | * **Memory access** (MEM): Dacă este necesar, se accesează memoria de date. | + | |
- | * **Register write back** (WB): Dacă este necesar, se scriu date în registrele de procesor. | + | |
- | O instrucțiune nu trebuie să folosească toate stagiile disponibile. Spre exemplu, pe o arhitectura [[https://en.wikipedia.org/wiki/Load/store_architecture|load/store]], doar instrucțiunile //load// și //store// accesează memoria, deci folosesc etapa MEM, restul folosesc doar registrele de procesor, deci folosesc etapa WB. | + | La fiecare front negativ de ceas, deci la finalul fiecărui ciclu, controller-ul vaverifica semnalul de reset, care va reseta orice instrucțiune se execută și va trece înapoi în stage 0 (IF). |
- | ==== 2. Pipelining ==== | + | În orice alt caz el va trece circular (deci de la WB vom trece înapoi în IF) la următorul stagiu de execuție a instrucțiunii curente: |
- | Execuția normală a instrucțiunilor are loc în mod secvențial. Deci execuția unei instrucțiuni va începe după terminarea ultimului stagiu al instrucțiunii precedente. Astfel, considerând modelul descris mai sus, pentru a executa 3 instrucțiuni avem nevoie de 15 cicli de ceas (considerând că toate instrucțiunile folosesc toate stagiile). | + | ''IF -> ID -> EX -> MEM -> WB'' |
- | {{ :soc:laboratoare:09:ex_no_pipeline.png?700 |Execuția fără pipeline}} | + | De asemenea, din memoria ROM a controller-ului, la fiecare modificare a stagiului de execuție sau a operației curente, acesta va verifica dacă este cazul unei operații de jump (JZ - Jump Zero, JNZ - Jump Not Zero, JM - Jump Minus) |
- | A executa instrucțiuni în pipeline înseamnă că există câte o instrucțiune în fiecare stagiu la orice moment dat. În timp ce o instrucțiune este în stagiul de execuție, alta este decodificată, alta este adusă din memorie etc. Asta duce la o reducere semnificativă a timpului de execuție al unui program. În același timp în care puteam executa doar 3 instrucțiuni fără pipeline, putem executa 11 instrucțiuni în pipeline. | + | Acest lucru este posibil deoarece controller-ul folosește o memorie ROM pentru a decide ce semnale de control trebuie activate, în funcție de instrucțiunea curentă și stagiul de execuție. Fiecare instrucțiune are 8 biți, iar stagiul are 4 biți, deci ele sunt combinate într-o adresa de 12 biți sub formă IIIIIIII TTTT. De exemplu, pentru instrucțiunea ADD B (codul 80) aflată în stagiul 3, adresa calculată este 803. În acea poziție din ROM este stocat un cuvânt de control de 35 de biți, care indică exact ce semnale trebuie activate în acel moment – cum ar fi B_EN și ALU_LOAD pentru a realiza adunarea. Astfel, ROM-ul acționează că o "harta" pentru execuția fiecărei instrucțiuni, eliminând nevoia unui cod complicat și greu de întreținut, și permițând controller-ului să execute eficient și clar pașii necesari pentru orice instrucțiune, inclusiv verificarea și gestionarea condițiilor de jump. |
- | {{ :soc:laboratoare:09:ex_pipeline.png?700 |Execuția în pipeline}} | + | ==== Semnale de control ==== |
- | <note> | + | Inainte de a trece la semnalele de control, e important sa stim din laboratorul 8 ca memoria are doi registrii: |
- | Execuția fiecărei instrucțiuni în parte durează la fel de mult ca în varianta fără pipeline, însă execuția programului durează mai puțin. | + | * Memory Data Register (MDR) - Un registru ce tine date de care avem nevoie temporar (opcode spre exemplu) in memorie |
- | </note> | + | * Memory Address Register (MAR) - Un registru ce retine o adresa la care dorim sa scriem/sa citim din memoria RAM |
- | Trebuie totuși să aplicăm o constrângere asupra stagiilor: fiecare stagiu trebuie să dureze la fel de mult timp. | + | De asemenea, semnalele de EN (enable) pot fi analizate cel mai bine in fisierul **SAP2_top.v**. Pe scurt, acestea pun output-ul unui registru pe magistrala. |
+ | Intr-un mod sumar, cele 39 de semnale de control pot fi descrise astfel: | ||
- | ==== 3. Hazarde ==== | + | ^ Semnal ^ Explicatie ^ |
+ | | RET | Folosit doar in instructiunea de RET | | ||
+ | | CALL | Folosit doar in instructiunea de CALL addr | | ||
+ | | RAM_EN_H | Pune octetul superior din RAM[mar] in registrul de date | | ||
+ | | RAM_EN_L | Pune octetul inferior din RAM[mar] in registrul de date | | ||
+ | | RAM_LD | Incarca in RAM[mar] octetul inferior din registrul de date al memoriei | | ||
+ | | MDR_EN | Incarca output-ul memoriei pe magistrala sistemului | | ||
+ | | MDR_LD | Incarca octetul inferior de pe magistrala in octetul inferior al MDR | | ||
+ | | MAR_LD_H | Incarca octetul superior al unei adrese aflate pe magistrala in octetul superior al MAR | | ||
+ | | MAR_LD_L | Incarca octetul inferior al unei adrese aflate pe magistrala in octetul inferior al MAR | | ||
+ | | PC_EN | Pune pe magistrala valoarea program counter-ului | | ||
+ | | PC_LD | Incarca in modulul de program counter valoare de pe magistrala (folosit in instructiunile de jump, care trebuie sa sara la o anumita instructiune) | | ||
+ | | PC_INC | Incrementeaza program counter-ul (util cand avem de citit o valoare imediata pentru instructiunea folosita, precum MVI sau JMP) | | ||
+ | | IR_LD | Incarca in modulul de Instruction Register valoarea de pe magistrala (necesar la fiecare instructiune pentru a stii ce instructiune urmeaza_ | | ||
+ | | ALU_EN | Pune pe magistra rezultatul din UAL (spre exemplu pentru a pune valoarea rezultata din operatia de ADD A in registrul A) | | ||
+ | | ALU_LD | Incarca in ALU a doua valoare necesara operatiei curente | | ||
+ | | ALU_OP_0/1/2/3 | Reprezinta codificarea operatiei curente din ALU (spre exemplu, pentru operatia SUB codificata cu 1, ALU_OP_0 va fi 1, iar restul vor fi 0) | | ||
+ | | FLAG_LD_C/B/A | Incarca in modulul flags.v flag-urile rezultate din ultima operatie a UAL-ului | | ||
+ | | C/B/A_DEC | Decrementeaza valoarea registrului corespunzator | | ||
+ | | C/B/A_INC | Incrementeaza valoarea registrului corespunzator | | ||
+ | | C/B/A_EN | Pune pe magistrala valoarea unui registru | | ||
+ | | C/B/A_LD | Incarca in registrul corespunzator valoarea de pe magistrala | | ||
+ | | IO_A_LD | Incarca in registrul **ioa** (io address) primul bit negat de pe magistrala (util pentru a stii daca vrem sa incarcam octetul superior sau cel inferior in registrul de memoriei al io_module) | | ||
+ | | IO_LD | Incarca in memoria modulului de IO o valoare de pe magistrala. Decizia de a incarca octetul superior sau pe cel inferior este data de valoarea lui ioa | | ||
+ | | IO_EN | Pune pe magistrala output-ul modulului de IO | | ||
+ | | HLT | Incheie executia programului | | ||
+ | | END | Incheie executia instructiunii curente | | ||
- | O problemă introdusă de execuția în pipeline sunt hazardele. Un hazard este situația în care execuția următoarei instrucțiuni este imposibilă sau va produce rezultate greșite. | + | Toate aceste semnale sunt scrise in ctrl_rom_io.bin. Ne amintim din laboratorul 8 ca acesta contine cuvinte de control, in care fiecare valoarea reprezinta un semnal, spre exemplul cuvantul 100000000000000000000000000000000000000 indica faptul ca doar semnalul de END este activ. |
- | === 3.1. Hazarde de date === | + | Adresa din ROM este formata prin concatenarea opcode-ului (8 biti) cu stage-ului (4 biti). Spre exemplu, cuvantul de la adresa 000000010010 (al 18-lea cuvant de control) este format prin concatenarea opcode-ului 00000001 cu stage-ul 0010. Deci acest cuvant de control contine semnalele ce trebuiesc executate de instructiunea codificata cu 8'h01, in stage-ul 2 al executiei. |
- | Un hazard de date se produce atunci când o instrucțiune are nevoie de un rezultat într-un stagiu al execuției, iar acel rezultat este calculat într-un stagiu al altei instrucțiuni ce va fi executat mai târziu. | + | ==== Instrucțiuni ==== |
- | ^ Hazard ^ Exemplu ^ Registru afectat ^ | + | Avem 39 de instrucțiuni, ce folosesc opcode-urile instrucțiunilor procesoarelor Intel 8080 ([[https://deramp.com/downloads/intel/8080%20Data%20Sheet.pdf]]) |
- | | Read After Write (RAW) | <code> | + | |
- | R2 <- R1 + R3 | + | |
- | R4 <- R2 + R2 | + | |
- | </code> | R2 | | + | |
- | | Write After Read (WAR) | <code> | + | |
- | R4 <- R1 + R5 | + | |
- | R5 <- R1 + R2 | + | |
- | </code> | R5 | | + | |
- | | Write After Write (WAW) | <code> | + | |
- | R2 <- R4 + R7 | + | |
- | R2 <- R1 + R3 | + | |
- | </code> | R2 | | + | |
- | Aceste hazarde sunt rezolvate prin [[https://en.wikipedia.org/wiki/Bubble_(computing)|bubbling]], execuția out-of-order sau pasarea operanzilor înainte (forwarding). | + | <spoiler Tabelă Instrucțiuni> |
+ | ^ Instrucțiune ^ Opcode ^ Ciclii de ceas ^ Flags ^ Metodă de adresare ^ Bytes ^ Descriere ^ | ||
+ | | ADD B | 80 | 5 | S,Z | Register | 1 | A = A + B | | ||
+ | | ADD C | 81 | 5 | S,Z | Register | 1 | A = A + C | | ||
+ | | ANA B | A0 | 5 | S,Z | Register | 1 | A = A & B | | ||
+ | | ANA C | A1 | 5 | S,Z | Register | 1 | A = A & C | | ||
+ | | ANI byte | E6 | 7 | S,Z | Immediate | 2 | A = A & byte | | ||
+ | | CALL addr | CD | 9 | - | Immediate | 3 | Call function at addr | | ||
+ | | CMA | 2F | 4 | - | Implied | 1 | A = ~A | | ||
+ | | DCR A | 3D | 4 | S,Z | Register | 1 | A = A - 1 | | ||
+ | | DCR B | 05 | 4 | S,Z | Register | 1 | B = B - 1 | | ||
+ | | DCR C | 0D | 4 | S,Z | Register | 1 | C = C - 1 | | ||
+ | | HLT | 76 | 4 | - | - | 1 | Halt execution | | ||
+ | | INR A | 3C | 4 | S,Z | Register | 1 | A = A + 1 | | ||
+ | | INR B | 04 | 4 | S,Z | Register | 1 | B = B + 1 | | ||
+ | | INR C | 0C | 4 | S,Z | Register | 1 | C = C + 1 | | ||
+ | | JMP addr | C3 | 8 | - | Immediate | 3 | Jump to addr | | ||
+ | | JM addr | FA | 4/8 | - | Immediate | 3 | Jump to addr if S Flag == 1 | | ||
+ | | JNZ addr | C2 | 4/8 | - | Immediate | 3 | Jump to addr if Z Flag == 0 | | ||
+ | | JZ addr | CA | 4/8 | - | Immediate | 3 | Jump to addr if Z flag == 1 | | ||
+ | | LDA addr | 3A | 10 | - | Direct | 3 | Load A with value at addr | | ||
+ | | MOV A, B | 78 | 4 | - | Register | 1 | A = B | | ||
+ | | MOV A, C | 79 | 4 | - | Register | 1 | A = C | | ||
+ | | MOV B, A | 47 | 4 | - | Register | 1 | B = A | | ||
+ | | MOV B, C | 41 | 4 | - | Register | 1 | B = C | | ||
+ | | MOV C, A | 4F | 4 | - | Register | 1 | C = A | | ||
+ | | MOV C, B | 48 | 4 | - | Register | 1 | C = B | | ||
+ | | MVI A, byte | 3E | 6 | - | Immediate | 2 | A = byte | | ||
+ | | MVI B, byte | 06 | 6 | - | Immediate | 2 | B = byte | | ||
+ | | MVI C, byte | 0E | 6 | - | Immediate | 2 | C = byte | | ||
+ | | NOP | 00 | 3 | - | - | 1 | Do nothing | | ||
+ | | ORA B | B0 | 5 | S,Z | Register | 1 | A = A OR B | | ||
+ | | ORA C | B1 | 5 | S,Z | Register | 1 | A = A OR C | | ||
+ | | ORI byte | F6 | 7 | S,Z | Immediate | 2 | A = A OR byte | | ||
+ | | RAL | 17 | 4 | - | Implied | 1 | A « 1, LSB becomes zero | | ||
+ | | RAR | 1F | 4 | - | Implied | 1 | A » 1, MSB goes to LSB | | ||
+ | | RET | C9 | 5 | - | Implied | 1 | Return from function | | ||
+ | | STA addr | 32 | 9 | - | Direct | 3 | Store value in A at addr | | ||
+ | | SUB B | 90 | 5 | S,Z | Register | 1 | A = A - B | | ||
+ | | SUB C | 91 | 5 | S,Z | Register | 1 | A = A - C | | ||
+ | | XRA B | A8 | 5 | S,Z | Register | 1 | A = A XOR B | | ||
+ | | XRA C | A9 | 5 | S,Z | Register | 1 | A = A XOR C | | ||
+ | | XRI byte | EE | 7 | S,Z | Immediate | 2 | A = A XOR byte | | ||
+ | </spoiler> | ||
- | <note> | + | Instrucțiunile pot fi împărțite în 4 categorii în funcție de modul în care adresează memoria în opcode: |
- | Hazardele WAR si WAW pot apărea numai în medii de execuție concurente (e.g. procesoare multi-core). | + | * Direct - Primește adresa de memorie pe care se operează în instrucțiune - ex. STA addr (Store la o anumită adresa) |
- | </note> | + | * Immediate - Primește o valoare constantă direct folosită în instrucțiune - ex. ORI byte (Aplica operatia de OR logic între registrul A și o valoare primită) |
+ | * Register - Funcționează pe datele dintr-un registru - ex. DCR A - Decrementează valoare registrului A | ||
+ | * Implied - Adresa/Registrul pe care se efectuează instrucțiunea este implicită și nu se modifică - ex. RAL (Shiftează la stânga valoarea din registrul A și o salvează înapoi tot în acesta) | ||
- | === 3.2. Hazarde structurale === | + | Sistemul nostru - SOC-1 - ia 3 ciclii de ceas pentru aducerea instrucțiunii de executat din memorie, deci cea mai scurtă instrucțiune va dura 3 ciclii (NOP). Cele mai lungi instrucțiuni sunt cele care lucrează cu adrese din memoria ROM și ajung până la 9-10 ciclii de ceas (STA - 9 ciclii, CALL - 9 ciclii, LDA - 10 ciclii) |
- | Hazardele structurale au loc atunci când două instrucțiuni trebuie să folosească aceeași unitate hardware în același timp. Un astfel de hazard are loc, spre exemplu, într-un sistem cu o singură memorie când se încearcă citirea unei instrucțiuni în același timp cu scrierea rezultatului altei instrucțiuni în aceeași memorie. | + | Instrucțiunile de jump durează 4 sau 8 ciclii de ceas în funcție de rezultatul comparației din controller. Dacă în controller condiția eșuează și jump-ul nu este executat, acesta durează 4 ciclii de ceas. În schimb, dacă acesta este executat, instrucțiunea de jump durează 8 ciclii. |
- | Aceste hazarde sunt rezolvate prin separarea unităților astfel încât această situație să nu se poată întâmpla (e.g. separarea memoriei de instrucțiuni față de memoria de date) sau prin bubbling. | + | ==== Implementarea unei instructiuni ==== |
- | === 3.3. Hazarde de control === | + | Pentru implementarea unei instructiuni sunt necesari pasii urmatori: |
+ | - Intelegerea semnalelor de control | ||
+ | - Determinarea modulelor implicare in executia unei instructiuni si a flow-ului datelor prin acestea | ||
+ | - Prelucrarea datelor in modulele corespunzatoarea (spre exemplu, prelucrarea datelor in UAL in timpul instructiunii de ADD) | ||
+ | - Scrierea semnalelor pentru fiecare stage si modificarea ROM-ului controller-ului cu semnalele corespunzatoare | ||
- | Hazardele de control au loc după instrucțiunile de salt condițional. Problema aici este că procesorul trebuie să încarce următoarea instrucțiune înainte de a ști rezultatul saltului, deci s-ar putea să încarce instrucțiunea greșită. În cazul unei predicții eronate, instrucțiunea încărcată trebuie eliminată din pipeline. | + | ==== Scrierea și execuția de cod pe SOC-1 ==== |
- | Aceste hazarde pot fi rezolvate prin bubbling, dar de obicei sunt tratate de unități specializate de [[https://en.wikipedia.org/wiki/Branch_predictor|branch prediction]]. | + | După cum v-a fost zis la începutul anului, sistemul acesta de calcul este capabil să execute și cod. Deoarece SOC-1 funcționează pe o parte din ISA-ul (Instruction Set Architecture - pe scurt, cum sunt definite instrucțiunile) de la Intel 8080, putem scrie o secvență simplă de cod pentru acest procesor pe care să o executăm: |
- | ==== 4. Exerciții ==== | + | <code> |
+ | MVI A, 0 ; A = 0 | ||
+ | MVI B, 12 ; B = 12 | ||
+ | MVI C, 8 ; C = 8 | ||
- | Vom implementa un procesor extrem de simplu, ce execută instrucțiuni în 5 stagii: IF, ID, EX, MEM și WB. | + | REPEAT: |
+ | ADD B ; A += B | ||
+ | DCR C ; C-- | ||
+ | JZ DONE ; if (C == 0) go to DONE | ||
+ | JMP REPEAT ; else go to REPEAT | ||
- | **Task 0** și **Task 1** presupun realizarea unui procesor ce va fi capabil să execute următoarele instrucțiuni din memoria de program: | + | DONE: |
- | <code> | + | HLT |
- | xor r0, r1 | + | </code> |
- | and r2, r3 | + | |
- | or r4, r5 | + | |
- | neg r6 | + | |
- | add r7, r8 | + | |
- | sub r7, r2 | + | |
- | mod r7, r2 | + | |
- | div r20, r2 | + | |
- | mul r4, r2 | + | |
- | div r6, r4 | + | |
- | add r0, r7 | + | |
- | add r4, r6 | + | |
- | add r2, r20 | + | |
- | add r0, r4 | + | |
- | add r0, r2</code> | + | |
- | **Task 0 (3p)** - Implementați logica procesorului făra pipeline în **task01.v**. | + | <spoiler ISA> |
- | * Urmariți TODO-urile. | + | Orice procesor are definit un Instruction Set Architecture (ISA) care definește: |
- | * Notați numărul de cicluri de ceas până la terminarea ultimei instrucțiuni din **task02.v** (memoria pentru instrucțiuni). | + | * Instrucțiunile recunoscute de către procesor |
- | <note tip> | + | * Tipurile de date recunoscute |
- | Structura unei instrucțiuni este următoarea: ''Cod instrucțiune (16 biți) | operand 1 (8 biți) | operand 2 (8 biți)'', unde fiecare operand reprezintă indexul unui registru. | + | * Contextul de execuție al unei instrucțiuni |
+ | * Interacțiunea dintre mai multe instrucțiuni (spre exemplu cum ați propagat flag-urile de Carry/Overflow în laboratorul anterior, sau executarea de salturi condiționale) | ||
+ | </spoiler> | ||
- | e.g. instrucțiunea ''ADD R1, R1'' este codificată astfel: ''0000000000000001 00000001 00000001''. | + | ==== TL;DR ==== |
+ | * Pentru execuția instrucțiunilor, sistemul de calcul trebuie să treacă prin etapele: ''IF -> ID -> EX -> MEM -> WB'' | ||
+ | * Controller-ul decide ce semnale de control trebuie să activeze cu ajutorul memoriei sale ROM | ||
+ | * Sistemul de calcul SOC-1 folosește instrucțiuni ale procesorului Intel 8080, și prin urmare poate executa cod compatibil cu acesta. | ||
+ | ==== Exercitii laborator ==== | ||
+ | |||
+ | - (0p): Analizati pe baza sheet-ului de instructiuni cu asistentul/asistenta de laborator secventa de mai sus pentru a determina ce se va intampla de-alungul executiei acesteia. | ||
+ | - (5p) (Branch Group Instructions): Observati cum este implementata instructiunea de JM (Jump Minus) si implementati similar si operatia de JP (Jump Positive) in modulul controller.v. Vom defini opcode-ul instructiunii JP ca fiind 8'hFB. Ulterior, modificati in sap2_ctrl_rom.xlsx semnalele de control necesare fiecarei etape de executie a instructiunii. Dupa ce le-ati modificat, copiati coloana cu semnalele concatenate si puneti-le in ctrl_rom_io.bin. | ||
+ | - (5p) (Arithmetic Group Instructions): Implementati in modulul UAL instrucțiunile CMP B si CMP C, care compara prin scadere valoarea lui A cu a lui B, respectiv C si modifica flag-urile pe baza acestor operatii. Opcode-urile pentru CMP B si CMP C in ALU sunt 8 si 9, iar in ROM si excel sunt 8'h92 si 8'h93. Modificati semnalele si adaugati-le in ctrl_rom_io.bin ca la task-ul de mai sus. | ||
+ | - (0p): Cum va usureaza munca instrucțiunile implementate la task 2 fata de utilizarea unor operatii normale de SUB | ||
+ | - (Bonus) (I/O Group Instructions): Implementati instructiunea de I/O IN data, care se va primi prin intermediul switch-urilor o valoare pe 4 biti pe care o veti stoca in sectiunea LOW a registrului A. Instructiunea de IN poate fi gasita ca 0'hDB. Pentru ajutor puteti vedea si instructiunea de OUT ca 0'hD3. Modificati semnalele si ctrl_rom_io ca la primele 2 task-uri. | ||
+ | |||
+ | Daca functia concatenate din excel nu este recunoscuta cand deschideti local, incercati folosirea Google Spreadsheets sau OneDrive. | ||
+ | |||
+ | <spoiler SUB vs CMP> | ||
+ | <code> | ||
+ | MVI A, 5AH | ||
+ | MVI B, 64H | ||
+ | SUB B ; Modificam valoarea lui A | ||
+ | JC B_IS_GREATER | ||
+ | </code> | ||
+ | |||
+ | <code> | ||
+ | MVI A, 5AH | ||
+ | MVI B, 64H | ||
+ | CMP B ; Comparam A cu B si setam flag urile operatiei A - B fara a stoca rezultatul. Util in cazul in care avem nevoie de A mai tarziu si nu vrem sa folosim inca un registru | ||
+ | JC B_IS_GREATER | ||
+ | </code> | ||
+ | </spoiler> | ||
- | Instrucțiunea ''NEG'' are un singur operand (''operand 1''). | + | <hidden> |
- | </note> | + | Pentru fiecare task studentii vor avea de implementat logica instructiunii in modulul corespunzator si de modificat semnalele de control din excel. Mai jos aveti atasata arhiva solutiei. |
- | **Task 1 (6p)** - Implementați logica procesorului cu pipeline în **task11.v**. | + | Pentru operatia de **JP (Jump Positive)**, semnalele vor fi identice cu cele de la Jump Minus, fiind primul task vrem sa vedem ca studentii macar inteleg sumar ce se intampla cu acele semnale. |
- | * Urmariți TODO-urile. | + | |
- | * Observați rezolvarea hazardelor din **task12.v**. | + | |
- | * Notați numărul de cicluri de ceas până la terminarea ultimei instrucțiuni din **task12.v**. | + | |
- | <note tip> | + | Pentru operatiile de **CMP**, implementarea va fi identica cu cea a operatiei de SUB, iar semnalele vor fi similare. CMP B difera de SUB B prin faptul ca nu vom mai activa semnalul de A_LOAD (nu vom scrie rezultatul in registrul A). Similar si pentru CMP C. |
- | Din moment ce unele componente ale pipeline-ului folosesc aceleași elemente, avem nevoie să reținem într-o altă variabilă elementele scrise de o componentă în ciclul anterior de ceas, de care are nevoie acum componenta următoare. | + | |
- | Variabilele care sunt folosite de componente consecutive ale pipeline-ului trebuie reținute și într-un registru special buffered. Spre exemplu, între //memory// și //decoder// avem variabila **instruction** folosită de ambele. Într-un ciclu de ceas, //memory// scrie în **instruction**, dar //decoder// are nevoie de valoarea de la ciclul de ceas anterior. Pentru aceasta, //decoder// va primi variabila **instruction_buffer**, care are valoarea lui **instruction** din ciclul anterior de ceas. | + | In **simulare**, daca operatiile au fost implementate cu succes, se va efectua A = 3, B = 2, CMP, JP, HLT, iar simularea se va opri dupa 280.000ns. Daca nu este recunoscuta instructiunea, va ramane blocat pe aceasta. Daca sunt implementate gresit, nu se va executa JP la adresa 0x0B (HLT) ci se va executa JMP 0x00 (inapoi la inceputul programului). |
- | </note> | + | |
- | **Task 2 (2.5p)** - Completați memoria de program din **task21.v** astfel încât la final să se afle în registrul 0 valorea în zecimal 1234. (**HINT**: Ce valori se află inițial în registrele procesorului?) | + | Pentru a verifica doar functionalitatea lui JP, studentii pot inlocui opcode-ul instructiunii de CMP cu cel al instructiunii de SUB B. Daca JP e implementata corect, simularea se va opri ca mai sus, dupa 280.000ns. |
- | **Task 3 (0.5p)** - Completați memoria de program din **task31.v** astfel încât la final să se afle în registrul 0 valorea în zecimal 1234. Fiți atenți la posibile hazarde! | + | Operatia de **IN byte** este similara cu cea de OUT pe care am dat-o ca referinta. Primele 3 etape sunt identice la toate instructiunile, a 4-a etapa va pune pe magistrala datele din MDR, iar in etapa urmatoare vrem sa le incarcam in IOA (adresa registrului de IO). In urmatoarea (si ultima etapa) vrem sa trimitem datele din IO module (cu IO_EN) si sa le incarcam in registrul A (A_LD). |
+ | </hidden> | ||
- | <note tip> | + | Pentru mai multe detalii cu privire la instructiunile de mai sus, va recomandam sa cititi din datasheet-ul de mai jos paginile 34-45 |
- | Când simulați orice task se va deschide fișierul ''Default.wcfg''. Acesta are toate semnalele incluse pentru debugging. Aveți grijă să nu suprascrieți ''Default.wcfg'' când ieșiți din simulator. | + | |
- | </note> | + | |
- | ==== Resurse ==== | + | ==== Resurse ==== |
+ | [[https://deramp.com/downloads/intel/8080%20Data%20Sheet.pdf]] | ||
- | * [[https://github.com/cs-pub-ro/SOC/tree/main/lab09|Scheletul de laborator]] | + | <hidden> |
+ | {{:soc:laboratoare:sap-2_lab9_solutie.zip | Solutia laboratorului}} | ||
+ | Daca fisierul xdc nu este recunoscut automat, doar dati-i add source din Vivado. | ||
+ | </hidden> | ||
+ | {{:soc:laboratoare:sap-2_lab9_skel.zip | Scheletul laboratorului}} |