În acest laborator vom studia 2 tipuri noi de instrucțiuni din cadrul ISA: instrucțiuni de control și instrucțiuni care lucrează cu stiva.
În funcție de modul în care se realizează saltul, instrucțiunile de control pot fi împărțite în mai multe categorii:
jump
): cele care determină continuarea rulării de la o anumită adresă (instrucțiune)branch
/ alegeri): continuarea execuției de la o anumită instrucțiune doar dacă se îndeplinește o anumită condițiecall
, ret
): un salt special la un anumit grup de instrucțiuni “îndepărtat”. După terminarea execuției acelor instrucțiuni, se revine la execuția normală a codului, continuând de la adresa unde s-a facut apelul de funcție, in timp ce apelurile precedente erau “one way”.halt
) este considerată tot o instrucțiune de control.În continuare, ne vom ocupa de urmatoarele instructiuni:
Pentru a înțelege efectul salturilor și modul în care acestea funcționează, trebuie mai întâi să înțelegem ce este un program counter (PC) și care este rolul său.
Secvența de cod pe care o are de executat un procesor se află de cele mai multe ori stocată într-o memorie de tip ROM conectată la acesta. Pentru a decodifica și executa corect instrucțiunile din acea memorie, procesorul trebuie să știe tot timpul care este adresa de la care trebuie adusă o instrucțiune în etapa de fetch. Această sarcină este îndeplinită de registrul PC
(program counter). Rolul acestui registru este de a reține adresa de la care trebuie adusă următoarea instrucțiune din memorie. Mai este numit și Instruction Pointer deoarece funcționează exact ca un pointer: reține o adresă și ajută procesorul să obțină datele stocate la acea adresă.
În mod normal, de fiecare dată când procesorul se află în stagiul ID
al pipeline-ului, după ce instrucțiunea a fost adusă pentru decodificare, program counter-ul va fi incrementat. Totuși, există și alte situații în care valoarea program counter-ului poate fi modificată. De cele mai multe ori, instrucțiunile de salt sunt responsabile pentru modificarea PC-ului în afara stagiului de I
nstruction D
ecode.
Salturile necondiționate sunt instrucțiuni ce modifică valoarea P
rogram C
ounter-ului cu o valoare fixă. Acest tip de salturi nu ține cont de evenimente produse în timpul rulării sau de anumite registre ce țin informații legate de operațiile executate recent de către procesor.
În general, instrucțiunile de salt necondiționate sunt codificate ca instrucțiuni cu un singur operand, operandul fiind o constanta care determină cu cât se modifică P
rogram C
ounter-ul (peste câte adrese va sări).
Acest tip de salturi este în general folosit atunci când dorim să sărim peste o zonă de memorie deoarece conține implementarea unei rutine pe care nu dorim să o executăm sau dacă dorim să evităm o serie de instrucțiuni ce prezintă un posibil hazard. În limbajele high level, saltul necondiționat este cel mai des reprezentat prin tag-uri GOTO
.
Un alt tip de salturi sunt cele condiționate. Acestea au același efect, acela de a modifica valoarea P
rogram C
ounter-ului, însă nu îl vor modifica de fiecare dată. Atunci când se execută o instrucțiune de salt condiționat, se verifică anumite criterii, în funcție de tipul instrucțiunii. Dacă acele criterii sunt îndeplinite, se va realiza saltul. Altfel, P
rogram C
ounter-ul continuă să fie incrementat în mod normal, în stagiul ID
. Salturile condiționale sunt cel mai ușor asemănate cu instrucțiunile de tip if
din limbajele de programare de nivel înalt.
Deoarece aceste instrucțiuni verifică îndeplinirea unor condiții pentru a executa salturi, este nevoie de un mod de a reține starea parametrilor verificați. De cele mai multe ori, acești parametri reprezintă evenimente ce au avut loc în urma executării unor operații de către UAL:
Pentru a reține toate aceste evenimente se folosesc flag-uri
grupate în ceea ce se numește Processor Status Register
sau SREG
(este oarecum echivalentul registrului flags
din arhitectura x86).
Printre flag-urile des folosite din SREG se află:
Z
(Zero) - indică dacă rezultatul unei operații aritmetice este zeroC
(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ă. Î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. 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.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”. 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.
Scrierea biților cu valorile corespunzătoare din SREG
revine UAL-ului. La execuția unei operații, se calculeaza și valorile fiecărui flag ce poate fi afectat de acel tip de operație.
Practic, ca și la arhitectura x86, putem considera că SREG
este un registru global, a cărui valoare este setată de ultima instrucțiune aritmetico-logică executată de procesor.
SREG
, consultați datasheet-ul ISA-ului. Modul în care acesta este modificat de fiecare instrucțiune este de asemenea descris în pagina corespunzatoare acesteia.
Există două tipuri de salturi, în funcție de adresa destinație:
P
rogram C
ounter-ul curent (adresa instrucțiunii de salt) și adresa destinației. Avantajul este că, pentru salturi apropiate, se salveaza biți pentru codificarea adresei, dar dezavantajul este că nu se poate sări prea departe.
Pentru AVR, salturile condiționate (branch-urile) sunt întotdeauna autorelative, iar cele necondiționate (jump-urile) vin în două variante: unele relative (RJMP
pe 16 biți), și altele absolute (JMP
pe 32 de biți, pe care este dificil să o implementăm pe procesorul nostru cu instrucțiuni de 16 biți). În cazul instrucțiunii JMP
, adresa destinație este o valoare imediată pe 22 de biți, permițând saltul la orice adresă în spațiul de 4M (de fapt, 8 megabytes, ținând cont că fiecare instrucțiune are 16 biți). Pe de alta parte, RJMP
cu cei 12 biți de valoare imediată, poate executa un salt la “doar” +/- 2K instrucțiuni relativ la cea curentă.
Încă și mai ciudat, există și CALL
/RCALL
, prin care se poate specifica adresa unei funcții atât ca valoare absolută, cât și relativă. Considerentele sunt aceleași: economisirea memoriei de instrucțiuni (32 de biți pentru CALL
vs 16 pentru RCALL
), dar și simplificarea logicii de I
nstruction F
etch și I
nstruction D
ecode.
Din alt considerent, există jump-uri și branch-uri la adrese:
În acest laborator vom implementa doar salturi la adrese imediate.
Această pereche de instrucțiuni este folosită pentru a pune și a extrage date de pe stivă.
Așa cum este implementată stiva în AVR, index-ul cel mai mare este la baza stivei, iar cel mai mic în vârful acesteia.
Registrul PC sau program counter (PC) reține adresa de la care trebuie adusă urmatoarea instrucțiune din memorie. Mai este numit și Instruction Pointer (IP
) deoarece funcționeaza exact ca un pointer: reține o adresă și ajuta procesorul să obțină datele stocate la acea adresă.
În funcție de modul în care se realizează saltul, instrucțiunile de control pot fi împărțite în mai multe categorii:
call
, ret
) SP sau stack pointer-ul se va afla tot timpul pe poziția liberă cea mai apropiată de baza stivei.
PUSH, POP - această pereche de instrucțiuni este folosită pentru a pune și a extrage date de pe stivă.
În acest laborator stiva va fi mapată peste spațiul de adresă 0x40 - 0xBF. Inițial, stack pointer-ul se află la baza stivei (valoarea cea mai mare - în schelet: define STACK_START 8'hBF).
În laboratorul de astăzi vom implementa câteva dintre instrucțiunile de jump și branching menționate. Pentru fiecare instrucțiune va trebui să aduceți modificări următoarelor fișiere:
decode_unit.v
- aici se implementează logica pentru decodificarea instrucțiunilorcontrol_unit.v
- controller-ul este cel care se ocupă de modificarea program counter-ului. Pentru instrucțiunile de branch și jump, considerăm că această modificare se face în starea de EXECUTE
alu.v
- implementarea logicii de calcul a flag-urilor verificate în cadrul operațiilor de branch.signal_generation_unit.v
- Analizați cum sunt setate semnalele CONTROL_STACK_POSTDEC și CONTROL_STACK_PREINC.Task 1 (1p). Generați codul mașină corespunzator secvenței de cod de mai jos și copiați-l in memoria ROM. Simulați codul și folosind semnalele relevante explicați ce face codul dat.
ldi r16, 5 ldi r17, 15 push r16 push r17 main_loop: mov r30, r16 sub r30, r17 brbs 1, done brbs 2, label2 label1: sub r16, r17 rjmp main_loop label2: sub r17, r16 rjmp main_loop done: push r16 pop r20 pop r21 pop r22
Task 2 (2p). Implementați instrucțiunea RJMP
- salt necondiționat. Această operație are un singur operand: numărul de instrucțiuni peste care trebuie să sară program counter-ul.
Task 3 (1.5p). Implementați instrucțiunea BRBS
(BRanch if Bit in SREG is Set). BRBS este un exemplu de instrucțiune de control cu 2 operanzi. Al doilea operand este bit-ul din SREG
corespunzător flag-ului pe care dorim să îl verificăm. Această instrucțiune va face un salt doar dacă bit-ul selectat este 1.
Task 4 (1.5p). Implementați instrucțiunea BRBC
(BRanch if Bit in SREG is Cleared). BRBC este un alt exemplu de instrucțiune de control cu 2 operanzi. Operandul suplimentar este bit-ul din SREG
corespunzător flag-ului pe care dorim să îl verificăm. Această instrucțiune va face un salt doar dacă bit-ul selectat este 0.
Task 5 (2 x 2p). Implementați instrucțiunile PUSH și POP.
[2] Wikipedia: Atmel AVR instruction set
[3] Tool generare cod masina AVR
Pentru a folosi tool-ul de generare al codului masina AVR, pentru windows descărcați Java pentru cmd.