Î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.
Execuția instrucțiunilor este de regulă - mai ales în arhitecturile RISC - împărțită în 5 etape:
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.
Inainte de a trece la semnalele de control, e important sa stim din laboratorul 8 ca memoria are doi registrii:
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.
Avem 39 de instrucțiuni, ce folosesc opcode-urile instrucțiunilor procesoarelor Intel 8080 (https://deramp.com/downloads/intel/8080%20Data%20Sheet.pdf)
Instrucțiunile pot fi împărțite în 4 categorii în funcție de modul în care adresează memoria în opcode:
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.
Pentru implementarea unei instructiuni sunt necesari pasii urmatori:
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
IF → ID → EX → MEM → WB
Daca functia concatenate din excel nu este recunoscuta cand deschideti local, incercati folosirea Google Spreadsheets sau OneDrive.
Pentru mai multe detalii cu privire la instructiunile de mai sus, va recomandam sa cititi din datasheet-ul de mai jos paginile 34-45