Laboratorul 9 - Proiectul SOC-1 - Continuare

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

Stagiile de execuție a 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.

Unitatea de control

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

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

IF → ID → EX → MEM → WB

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)

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.

Semnale de control

Inainte de a trece la semnalele de control, e important sa stim din laboratorul 8 ca memoria are doi registrii:

  • Memory Data Register (MDR) - Un registru ce tine date de care avem nevoie temporar (opcode spre exemplu) in memorie
  • Memory Address Register (MAR) - Un registru ce retine o adresa la care dorim sa scriem/sa citim din memoria RAM

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:

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

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.

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.

Instrucțiuni

Avem 39 de instrucțiuni, ce folosesc opcode-urile instrucțiunilor procesoarelor Intel 8080 (https://deramp.com/downloads/intel/8080%20Data%20Sheet.pdf)

Tabelă Instrucțiuni

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

Instrucțiunile pot fi împărțite în 4 categorii în funcție de modul în care adresează memoria în opcode:

  • Direct - Primește adresa de memorie pe care se operează în instrucțiune - ex. STA addr (Store la o anumită adresa)
  • 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)

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)

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.

Implementarea unei instructiuni

Pentru implementarea unei instructiuni sunt necesari pasii urmatori:

  1. Intelegerea semnalelor de control
  2. Determinarea modulelor implicare in executia unei instructiuni si a flow-ului datelor prin acestea
  3. Prelucrarea datelor in modulele corespunzatoarea (spre exemplu, prelucrarea datelor in UAL in timpul instructiunii de ADD)
  4. Scrierea semnalelor pentru fiecare stage si modificarea ROM-ului controller-ului cu semnalele corespunzatoare

Scrierea și execuția de cod pe SOC-1

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:

MVI A, 0   ; A = 0
MVI B, 12  ; B = 12
MVI C, 8   ; C = 8

REPEAT:
ADD B       ; A += B
DCR C       ; C--
JZ DONE     ; if (C == 0) go to DONE
JMP REPEAT  ; else go to REPEAT 

DONE:
HLT

ISA

ISA

Orice procesor are definit un Instruction Set Architecture (ISA) care definește:

  • Instrucțiunile recunoscute de către procesor
  • Tipurile de date recunoscute
  • 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)

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

  1. (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.
  2. (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.
  3. (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.
  4. (0p): Cum va usureaza munca instrucțiunile implementate la task 2 fata de utilizarea unor operatii normale de SUB
  5. (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.

SUB vs CMP

SUB vs CMP

MVI A, 5AH
MVI B, 64H
SUB B          ; Modificam valoarea lui A
JC B_IS_GREATER
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

Pentru mai multe detalii cu privire la instructiunile de mai sus, va recomandam sa cititi din datasheet-ul de mai jos paginile 34-45

Resurse

soc/laboratoare/09.txt · Last modified: 2025/05/27 17:37 by stefan.maruntis
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0