Sunteți pe pagina 1din 21

cap 1.

Arhitectura procesorului i8086


Execuția unui program într-un sistem cu microprocesor are ca efect (simplificat) repetarea ciclică a pașilor de mai jos:
1)Extragerea următoarei instrucţiuni din memorie
2)Citirea unui operand(dacă instrucţiunea o cere)
3)Execuţia instrucţiunii
4) Scrierea rezultatului (dacă instrucţiunea o cere)
Datorită arhitecturii specifice, execuţia acestor paşi are loc în două unităţi separate de procesare a datelor din cadru
CPU(Unitatea de procesare) : unitatea de execuţie(EU) şi unitatea de interfaţă cu magistrala(BIU). În EU are loc execuţia
instrucţiunulor, în timp ce BIU extrage instrucţiunile citeşte operanzii şi scrie operanzii în memorie. Cele două unităţi pot
opera independent şi pot asigura(în majoritatea cazurilor) suprapunerea în timp a etapei de extragere a unei instrucţiuni
cu etapa de execuţie a unei instrucţiuni precedent extrase din memorie. În acest mod, practic "dispare" timpul necesar
extragerii instrucţiunilor din memorie, crescând viteza de lucru a procesului deoarece EU execută insrtrucţiuni al căror cod
a fost deja adus de către BIU din memorie în procesor.
Figura 1-1 ilustrează această suprapunere în timp a fazelor de extragere şi de execuţie a instrucţiunilor printr-un
exemplu în care timpul necesar execuţiei a 3 instrucţiuni este mai mic de la 8086 în comparaţie cu microproceoarele din
generaţia precedentă, două instrucţiuni în plus fiind deja extrase şi aşteptând să fie executate.
1.1 EU- Unitatea de execuţie
Unitatea de execuţie conţine regiştri de uz general, unitatea aritmetico-logică(UAL/ALU) , registrul indicator de
condiţie (flags), un bloc logic de control şi o magistrală internă de date de 16 biţi(figura 1-2).
Funcţiile EU acoperă exeucţia tuturor instrucţiunilor, furnizarea datelor şi a adreselor către BIU , controlul registrelor
den uz general şi indicatorul de condiţie.(registru=zonă de memorie cu lungimea unui cuvânt). magistrala de adrese:
1K(memorie) => 10 linii de adrese, 1M=>20 linii de adrese etc.
Cu excepţia câtorva timpi de control, unitatea de execuţie este complet iyolată de "lumea exterioară". Aşa cum se
vede în figura 1-2, EU preia instrucţiunea următoare de executat dintr-o coadă de aşteptare alimentată continuu de BIU.
În situaţia în care nu există nicio instrucţiune de preluat pentru a fi executată, EU aşteaptă până când coada este
realimentată de către BIU. Dacă în cursul execuţiei instrucţiunii este necesar accesul la o locaţie de memorie sau la un
echiăament periferic, EU solicită lui BIU să transfere data, executând ciclu de magistrală corespunzător(citire/scriere la
memorie sau port). Totodată, deşi magistrala EU are doar 16 biţi, se poate accesa în exterior întregul spaţiu de 1MO de
memorie prin intermediul BIU care asigură relocarea adresei înaintea fiecărui transfer.
1.2 BIU- Unitatea de interfaţă cu magistrala
BIU execută toate operaţiile externe de magistrală ce sunt necesare pe parcursul extragerii şi execuţiei unei
instrucţiuni. Ea se compune din regiştri de segment, un registru de tip contor de program denumit pointer de instrucţiuni,
regiştri de comunicaţie internă, o schemă logică pentru generarea adresei pe cele 20 de linii de adresă ale
microprocesorului 8086 şi pentru controlul magistralei multiplexate(în loc de doupă magistrale, există o singură
magistrală) precum şi o coadă de instrucţiuni. Aceasta este realizată cu o memorie RAM de 6 octeţi şi conţine instrucţiuni
care sunt extrase în avans de BIU şi urmează să fie preluate de EU pentru decodificare şi execuţie.
Cele două unităţi de procesare ale CPU operează independent una de alta, în sensul că oridecâteori 2 sau mai mulţi
octeţi din coadă sunt liberi, iar EU nu solicită BIU la efectuarea unui ciclu de magistrală , BIU execută în avans ciclu de
extragere de insturcţiuni pentru a realimenta locaţiile libere din coada de instrucţiuni. Acest mod de lucru permite BIU să
furnizeze către EU instrucţiuni extrase anterior fără a monopoliza magistrala sistemzului, căci în mod normal, în
majoritatea situaţiilor, coada de instrucţiuni conţine cel puţin un octet ce poate fi preluat de EU pentru decodificare şi
execuţie. În plus, cum sistemele cu microprocesor 8086 au uzual magistrala de date de 16 biţi, într-un singur ciclu de
extragere se alimentează coada cu 2 octeţi, cu excepţia cazurilor când adresa de la care se citeşte instrucţiunea
următoare este impară.
Instrucţiunile extrase în avans de BIU sunt cele care urmează în mod logic într-o procesare serială a programului, ele
aflându-se în memorie în locaţii adiacente şi la adrese superioare adresei instrucţiunii care se execută la un moment dat.
În catzul în care EU execută o instrucţiune care transferă cotnrolul programului către o altă locaţie de memorie, BIU
resetează coada către o altă locaţie de memorie, BIU resetează coada, extrage instrucţiunea de la noua adresă,
transferând-o imediat lui EU, apoi începe redimentarea cozii de la noua locaţie. De asemenea, BIU suspendă operaţiile
de extragere de instrucţiuni(cu excepţia celei în curs de desfăşurare) oridecâteori EU solicită efectuarea pe magistrală
sau a unui transfer cu memoria sau cu un port de intrare/ieşire.
Regiștri:
AX- Acumulator(AH, AL), BX-Base(BH, BL), CX-Count(CH,CL), DX-Data(DH,DL)
1.3 Regiștri de uz general
Microprocesorul 8086 are 8 registre generale de 16 biți grupate în 2 seturi a câte 4 setări fiecare: registrele de
date(uneori denumite grupul registrelor H&L) și registrele pointeri și indecși(denumite și grupul PHI) - figura 1-3
Un registru aparținând grupuuli H&L se caracterizează prin faptul că el poate fi adresat ca registru de 16 biți, dar se
compune din 5 entități de 8 biți(parte high & parte low) care pot fi adresate la rândul lor separat ca regiștri de 8 biți.
Registrele pointer și index nu pot fi adresate decât ca registre de 16 biți. SP-Stack Pointer, BS-Base Pointer, SI-Source
Index, DI-Destination Index. Atât registrele de date cât și registrele pointer și index pot fi folosite în majoritatea operațiilor
aritmetice și logice, oricare dintre ele putând juca rolul registrului acumulator existent la generațiile precedente de
microporocesor. Pentru a permite utilizarea unui set compact dar puternic de instrucțiuni, anumite registre sunt folosite în
mod implicit de unele instr. așa cum arată tabelul 1.1.
1.4 Registrele de segment
Spațiul fizic de memorie de 1MO direct adresabil de microprocesorul 8086 este divizat în segmente logice de până la
64 KO. CPU are acces direct în orice moment la 4 segmente logice ale căror adrese de bază(adresele de început ale
segmentelor) se află în regiștri de segment ai microprocesorului.
Registrul segmentului de cod(CS) conține adresa de început a segmentului din care sunt extrase
instrucțiunile(segmentul de cod).
Stiva programului se află în așa numitul segment de stivă către care pointează registrul segmentului de stivă(SS). Mai
există, de asemenea, două segmente de date, unul propriu-zis(DS) și unul suplimentar(ES), fiecăruia fiindu-i asociat câte
un registru de segment ce conține adresa de început respectivă. Și registrele de segment sunt accesibile programatorului,
conținutul lor putând fi modificat de anumite instrucțiuni(figura 1-4): PUSH AX, BX / POP BX, AX.
1.5 Registrul pointerului de instrucțiuni
Este similar registrului contor de program(Program Counter) al microprocesorului pe 8 biți. Actualizat de către BIU, el
conține offset-ul(distanța în octeți) următoarei instrucțiuni, măsurat de la începutul segmentului curent de cod. IP
reprezintă în mo normal un pointer către următoarea instrucțiune ce urmează a fi extrasă de către BIU, iar atunci când
este salvat în stivă, se modifică automat pentru a indica offset-ul următoarei instrucțiuni ce urmează a fi executată de
către EU. Operarea cu cinținutul IP, odată salvat în stivă constituie calea pe care acesta poate fi modificat indirect în
decursul execuției unui program.
1.6 Indicatori de condiție
Microprocesorul 8086 are 6 biți den stare și 3 biți de control grupați în registrul indicatorilor de condiție(flags). Cei de
stare sunt poziționați de EU pentru a reflecta anumite proprietăți ale rezultatului unei operații aritmetice/logice. Aceștia pot
fi utilizați de un grup al setului de instrucțiuni pentru a modifica secvențialitatea execuției programului în funcție de
rezultatul operației anterioare. În general, indicatorii stării programului reflectă următoarele condiții:
- AF(auxiliary carry flag). Dacă AF=1,a existat un transport dinspre bitul 7 spre bitul 8 sau împrumut dinspre bitul 8 către
bitul 7. Acest indicator este folosit îndeosebi în cazul instrucțiunilor ce implică operații aritmetice cu numere zecimale
codificate binar.
-CF(carry flag). Dacă CF=1, a existat un transport dinspre sau un împrumut către cel mai semnificativ bit(MSB) al
rezultatului reprezentat pe 8 sau 16 biți. Acest indicator este utilizat de instrucțiunile ce indică operații de adunare sau
scădere cu numere reprezentate pe unul sau mai mulți octeți. Biți ai operanzilor din memorie sau regiștri pot fi izolați în
CF prin intermediul instrucțiunilor de rotire și deplasare.
-OF(Overflow flag). Dacă OF=1, a apărut o depășire aritmetică, adică s-a pierdut MSB al rezultatului datorită faptului că
dimensiunea acestuia a depășit capacitatea de reprezentare a locației destinației. Este de remarcat faptul că există o
instrucțiune (interrupt overflow) care generează o întrerupere pentru semnalarea apariției acestei situației.
-SF(Sign Flag).Dacă SF=1, MSB al rezultatului are valoarea 1. Cum numerele întregi cu semn sunt reprezentate în
complement față de 2, SF arată semnul rezultatului(0-pozitiv/1-negativ).
-PF(Parity Flag). Dacă PF=1, atunci rezultatul are un număr par de biți pozitionați pe 1. Poate fi utilizat pentru verificarea
erorilor la transmisia datelor.
-ZF(Zero Flag). Valoarea 0 a rezultatului este evidențiată prin ZF=1. Cei 3 indicatori de control pot fi modificați prin
program pentru a determina anumite operații ale procesorului astfel.
-DF(Direction Flag). Acest indicator este utilizat de instrucțiunile ce operează cu șirurile de caractere(string) și
semnalizează autodecrementarea(DF=1), respectiv autoincrementarea(DF=0) registrilor SI și DI care conțin offset-ul
adreselor sursă și destinație ce intervine în transfer.
-IF(Interrupt Enable Flag). Setarea IF prin instrucțiunea setinterrupt(STI Enable flag) permite CPU să recunoască cererile
de întrerupere externă marcabile în timp ce resetarea aceluiași indicator cu instr. clearinterrupt(CLI) le va dezactiva.
Modificarea valorii lui If nu are efect asupra întreruperilor generate intern în CPU sau a celor externe nemascabile.
-TF(Trac Flag). Dacă TF=1, procesorul intră în modul de operare pas cu pas în care CPU generează automat o
ăîntrerupere internă după fiecare instrucțiune pentru a opermite examinarea stării programului și deci, depanarea
acestuia(figurs 1-5)
cap.3. Structurarea programelor.Definirea și inițializarea datelor.Operatori
Capitolul curent și cel următor sunt dedicate limbajului de asamblare. Pe lângă instrucțiunile porpriu-zise, un program
poate cuprinde directive prin intermediul cărora se definesc date, etichete și proceduri, se structurează segmente, se
definesc și se utilizează macroinstrucțiuni, se controlează în general procesul de asamblare. Directivele nu reprezintă
instrucțiuni, ci comenzi către asamblor, efectul lor manifestându-se exclusiv în faza de asamblare.
3.1. Segmente. Directive pentru definirea segmentelor
Un modul de program în limbaj de asamblare poate conduce la:
-o porțiune dintr-un segment
-un segment
-porțiuni de segmente diferite
-mai multe segmente.
Mai multe module obiect se leagă prin apariția de link-editare.
3.1.1. Directivele segment și Ends
Indiferent de modul de dezvoltare a unui program ASM, atât instrucțiunile cât și datele trebuie să se găsească în
interiorul unui segment. Directiva segment controlează:
-numele segmentului
-alinierea
-combinarea cu alte segmente
-continuitatea(adiacența) segmentelor
Forma generală a directivei de segment este: cod 3.1
Parametrii incluși în ”[...]” sunt opționali. Dacă sunt prezenți, aceștia trebuie să fie în ordinea specificată. Semnificația
parametrilor este:
-tip aliniere : specifică la ce limită va fi relocat segmentul în memorie; poate avea una din formele:
 para(implicit) aliniere la paragraf(segmentul fizic adică adresa pe 20 de octeți va fi relocat la prima adresă
absolută divizibilă cu 16)
 byte : fără aliniere(segmentul se va reloca la următorul octet liber)
 word : aliniere la cuvânt(segmentul se va reloca la urmptorul octet liber aflat la adresa pară)
 sword: aliniere la dublu cuvânt(segmentul se va reloca la prima adresă divizibilă cu 21)
 page : aliniere la pagină(segmentul se va reloca la prima adresă absolută divizibilă cu 256)
-tip combinare : specifică dacă și cum se va recombina segmentul respectiv cu alte segmente la operația de linkeditare.
Poate fi:
 necombinabil(implicit) - nu se scrie nimic
 public - segmentul curent va fi concatenat cu alte segmente cu același nume și cu atributul public. Aceste
segmente pot fi în alte module de program.Se va forma un singur segment final cu numele respectiv, cu o unică
adresă de început:lungimea unui segment cu atributul public e suma lungimilor segmentelor cu același nume.
 common - specifică faptul că segmentul curent și toate segmentele cu același nume și tipul common se vor
suprapune în memorie(încep cu aceeași adresă fizică); lungimea unui segment common e cea mai mare lungime
a segmentelor componente.
 stack - marchează segmentul curent ca reprezentând stiva programului; dacă sunt mai multe segmente cu tipul
stack, ele vor fi tratate ca la public.
 AT Expresie - specifică faptul că segmentul va fi plasat la o adresă fizică absolută de memorie; următorul
exemplu ilustrează definirea explicită a tabelei de vectori de interpretare:cod 3.4.
 nume clasă - specifică un nume de clasă pentru un segment, extinzând astfel numele segmentului; de exemplu,
dacă segmentul are și atributul ”nume clasă”, atunci atributele ”common” sau ”public ” vor acționa numai
asupra segmentelor cu același nume și același nume de clasă; ca nume de clasă se folosesc de obicei: 'code',
'data', 'stack'.
3.1.2. Directiva ASSUME
Realizează o conexiune simbolică(logică) între definirea instrucțiunilor și a datelor în segmente logice(cuprinse între
segment și ends) la momentul asamblării și accesul la execuție , la instrucțiuni și date prin registrele de segment. Are
forma generală: cod 3.7 în care REG, SEG este un registru de segment, iar expresie poate fi:
-un nume de segment
-un nume de grup de segmente
-SEG nume de segment
-NOTHING
3.1.3. Directiva GROUP
Servește la gruparea mai multor segmente(inclusiv cu nume și atribute dfierite) a căror lungime nu depășește 64
KO, sub același nume. Are forma generală : cod 3.10.
Această directivă reprezintă o altă modalitate de combinare a mai multe segmente, pe lângă cea oferită de atributul
public.
3.2. Definirea simplificată a segmentelor
Avantajul major este faptul că se respectă același format(structură a programului obiect) ca și la programele
dezvoltate în limbaj de nivel înalt. Dacă utilizăm definirea simplificată, se vor genera segmente cu nume și atribute
identice cu cele generate de compilatoarelor de limbaj de nivel înalt. Toate directivele simplificate încep cu punct.
3.2.1. Modele de memorie
Directiva pentru specificarea modelului de memorie are forma generală: .mode tip în care tip poate fi: timy, small,
medium, compact, large sau huge. Semnificația acestor tipuri este :
- timy - toate segmentele (cod, date, stivă) se pot genera într-un spațiu de 64KO și formează un singur grup de
segmente. Toate salturile, apelurile de proceduri și definiții de proceduri sunt implicit de tip NEAR;
- small: datele și stiva sunt grupate într-un singur segment, iar codul în alt segment. Fiecre din acestea nu trebuie să
depășească 64KO. Toate salturile , apelurile și definișțiile de proceduri sunt implicit de tip NEAR;
- medium: datele și stiva sunt grupate într-un singur segment(cel mutl 64KO), dar codul pșoate fi în mai multe segmente
separate(nu se grupează), deci poate depăși 64KO. Salturile și apelurile sunt implicit de tip NEAR;
-compact : codul generat ocupă cel mult 64KO(se grupează), dar datele și stiva sunt în segmente separate(pot depăși
64KO). Apelurile și salturile sunt implicit de tip NEAR. Se utlizează adrese complete(segment și offset) atunci când se
acceseaă date definite în alte segmente.
- large : atât datele, cât și codul generat pot depăși 64KO.
- huge : este asemănător modului large, dar structurile de ate pot depăși 64KO
Folosirea directivelor simplificate prezintă și avantajul că nu mai sunt necesare directive ASSUME: asocierile între
segmente generate și registrele de segment implicite.
Se utilizează următoarea terminologie:
-modele de date mici: small, compact
-modele de cod mic:small, medium
-modele de date mari: medium, large, huge
-modele de cod mare: compact, large , huge.
3.2.2. Directive simplificate de definire a segmentelor
Formele generale ale acestor directive sunt: .stack, dimeniune, .cod[nume], .data, .data ?(datele neiniíalizate în limbaj
de nivel înalt), .fardata[nume], .fardata ?[nume](segmente de date utilizate prin adrese complete în limbae de nivel înalt-
ulitmile), .const(definirea de constante în limbaje de nivel înalt).
3.3 Directive pentru legarea modulelor
Când programul dezvoltat se compune din mai multe module asamblate separat, este necesară specificarea
simbolurilor care sunt definite într-un modul și utilizate în alte module. Aceste simboluri sunt nume de variabile, etichete
sau nume de proceduri. În mod normal un simbol este divizibil doar în modulul în care este definit(simbol local).
Simbolurile care sunt vizibile în mai multe module de program se numesc simboluri globale.
Se folosesc următoarele noțiuni:
 simboluri publice - sunt simboluri care sunt definite într-un modul curent de program și folosite(eventual) și în
alte module; aceste simboluri se declară ca simboluri publice în modulul în care sunt definite;
 simboluri externe - sunt simboluri care sunt folosite într-unul din modulele în care nu sutn definite; aceste
simboluri se declară ca simboluri externe în modulele în care sunt folosite;
Se observă că un simbol trebuie declarat ca simbol public în modulul în care este definit ca și simbol extern în
modulele în care este folosit, altele doar în cel care este definit.
Declaerația unui simbol public are forma generală: cod 3.19. Simboluri declarate ca publice pot fi variavbile, etichete,
nume de proceduri sau constante numerice simbolice.
Directiva extrn are forma generală cod 3.20 în care tip precizează tipul simbolului. Acesta poate fi:
-byte, word, dword, qword în cazul în care simbolul este o variabilă;
-near, far în cazul în care simbolul este o etichetă sau un nume de procedură;
-abs, în cazul în care simnbolul este o constantă numerică simbolică.
3.3.1. Directiva end
Marchează sfârșitul logic al unui modul de program și este obligatorie în toate modulele. Tot ce se găsește în fișierul
sursă după această directivă este ignorat la asamblare. Forma generală este cod 3.23 în care punct_de_strt este o
etichetă sau un nume de procedură ce marchează punctul în care se va da controlul după încărcarea programului în
memorie. Într-o aplicație compusă din mai multe module și care se constituie într-un program executabil, un singur modul
trebuie să aibă parametrul în directiva end.
3.4 Definirea și inițilizarea datelor
Asamblorul recunoaște 3 categorii sintactice de bază: constante, variabile și etichete (inclusiv nume de proceduri).
Constantele pot fi absolute (numerice) sau simbolice. Constantele simbolice reprezintă nume generice asociate unor
valori numerice. Variabilele identifică datele (un spațiu de memorie rezervat), iar etichetele identifică codul (program sau
procedură).
Instrucțiunile de tipul mov, add, and etc. folosesc variabile și constante, iar instr. jmp, call folosesc etichete.
Variabilele și etichetele au asociate atribute, cum ar fi segmentul în care sunt definite, offset-ul la care sunt definite în
interiorul segmentului etc.
3.4.1. Constante
Constantele numerice absolute pot fi:
-constante binare - se utilizează sufixul b sau B
-constante octale - se utilizează sufixele O,Q,o,q
-constante zecimale - fără sufix sau cu sufixele d/D
-constante hexazecimale - sufixele H/h și prefixul 0 deci prima cifră este mai mare ca 9; pentru cifrele peste 9 se
utilizează simbolurile a-f/A-F
-constante ASCII - se scriu unul sau mai multe caractere ASCII între semnul ' sau "; exemple de constante absolute: 123,
123D, 10001100B, 177q, 0AAH, 3fh, 'a', "AB".
Constantele simbolice se definesc cu directiva EQU în forma generală: cod 3.29, de exemplu liniile de program (cod
3.30) definesc constantele simbolice CR și LF cu valorile 0D și 0A din hexa. Putem folosi aceste constante simbolice în
orice context în care este permisă folosirea unei constante numerice.
3.4.2 Definirea și utilizarea variabilelor
Pentru definirea variabilelor se utilizează directivele DB, DW, DD,DQ,și DF. Forma generală a unei definiții de date
este cod 3.31 în care nume este identificatorul asociat funcției respective, iar lista de valori este lista valorilor inițiale care
poate cuprinde: constante numerice (absolute sau simbolice), simbolul ?, o adresă (adică un nume de variabilă sau
etichetă - se folosesc la DW, DD), un șir ASCII, operatorul dup(expresie) (dup-duplicate), în care expresie poate fi:
constantă numerică, listă de valori, simbolul ?, operatorul dup.
Semnificația simbolului '?' este locație neințializată, iar cea a operatorului dup repetarea de un număr de ori a expresiei
din paranteză (duplicate).
Să considerăm definițiile următoare : cod 3.32. Prima definiție este echivalentă cu cod 3.33. Atributele datelor definite
sunt:
-segment - cel curent
-offset - cel curent
-tip - 1,2,4,8,10 după cum s-a utilizat ca directivă de bază DB, DW, DD, DQ sau DT.
3.5 Definirea etichetelor. Directiva Label.
Etichetele sunt folosite pentru specificarea punctelor țintă la instrucțiuni de salt sau apel sau pentru o specificare
alternativă a datelor. În ambele cazuri, numele etichetei devine un nume simbolic asociat adresei curente de memorie.
Atributele etichetelor sunt : segment, offset, tip. Până acum s-au întâlnit două modalități de definire a etichetelor:
-prin nume urmat de caracterul ':' se definește o etichetă de tip NEAR
-prin directivă proc (procedură) - numele procedurii este interpretat ca o etichetă cu tipul derivat din tipul procedurii
O altă posibilitate este directiva LABEL care are forma generală cod 3.37.
3.6 Definirea structurilor. Operații specifice
Structurile reprezintă colecții de date (câmpuri sau membri) plasate succesiv în memorie, grupate sub un nume unic.
Dimensiunea unei structuri este suma dimensiunilor câmpurilor componente. Forma generală este cod 3.40.
Prin aceasta se definește tipul structurii (șablonul) fără a se rezerva spațiul de memorie. Definițiile de date pot
cuprinde directive DB, DW, etc cu valori inițiale asociate, ?, dup etc, exact ca la orice definiție de date. Numele membrilor
structurii trebuie să fie distincte chiar dacă aparțin unor structuri distincte. Un exemplu de definiție de tip structură : cod
3.41.
O structură definită este interpretată ca un nou tip de date care apoi participă la definiții concrete de date cu
rezervare de spațiu de memorie. De exemplu, putem defini o structură de tipul ALFA cu numele X: cod 3.42 în care ALFA
este tipul de date, X este numele variabilei iar între paranteze unghiulare se trec valorile inițiale ale câmpurilor structurilor
X. Prezența virgulelor din lista de valori inițiale precizează primii 2 membri sunt inițializați cu valorile implicite de la
definiția tipului ALFA, al III-lea membru este inițializat cu 777, iar al IV-lea cu valoarea 5. Astfel, definiția variabilei X este
echivalentă cu secvența de definiții: cod 3.43.
Principalul avantaj al structurilor este accesul la membri într-o formă asemănătoare limbajelor de nivel înlt. De
exemplu, pentru a încărca în SI câmpul B al structurii X, putem scrie : cod 3.44
Definirea înregistrărilor
Înregistrările(record) corespund de fapt unor structuri împachetate din limbajele de nivel înalt. Concret, o înregistrare
este o definiție de câmpuri de biți de lungime maximă 8/16. Din punct de vedere sintactic, definiția unei ănregistrări este
similară cu cea a unei structuri, forma generală fiind cod 3.52 sau cu inițializare implicită: cod 3.53.
Valorile care apar asociate cu numele de câmpuri dau de fapt numărul de biți pe care se memorează respectivul
câmp. În varianta cu inițializare, rxpresia care apare după semnul = se evaluează la o cantitate care se reprezintă pe
numărul de biți asociat câmpului respectiv. La fel ca la structuri, numele câmpurilor trebuie să fie distincte, chiar dacă
aparțin unor înregistrări diferite. Să considerăm un exemplu: cod 3.54 prin care se definește un șablon de 16 biți grupați în
câmpurile X,Y și Z(figura 3.2)
La fel ca la structuri, putem defini variabile de tipul înregistrare: cod 3.55 unde variabilele dintre parantezele < ,>
inițializează cele 3 câmpuri de biți.Se observă că această definiție este echivalentă cu definiția explicită: cod 3.56
Există doi operatori specifici înregistrărilor: MASK și WIDTH. Operatorul MASK primește un nume de câmp și
furnizează o mască cu biții 1 pe poziția câmpului respectiv și în rest 0. Astfel, expresiile MASK X și MASK Y sunt
echivalente cu constantele binare 1111111000000000B, 0000000111100000B.
Asemenea expresiei , pot fi utilizate pentru selectarea câmpurilor respecgtive ca în exemplele de mai jos : cod 3.57.
Operatorul WIDTH primește un nume de înregistrare sau un nume de câmp dintr-o înregistrare, întorcând numprul de
biți ai înregistrării sau ai câmpului respectiv. De exemplu, secvența cod 3.58 va încărca în al valoarea 16 și în bl valoarea
4.
Dacă se utilizează un nume de câmp într-o instrucțiune de tip MOV, se va încărca un contor de deplasare util pentru
a deplasa câmpul respectiv pe pozițiile cele mai puțin semnificative.
Să presupunem că dorim să testăm valoarea numerică a câmpului Y din înregistrarea val. Nu este suficient să izolăm
și să folosim o instrucțiune de comparație. Înainte de comparație, câmpul Y trebuie adus pe pozițiile cele mai din dreapta.
Realizăm aceste operații prin secvența: cod 3.59.
3.7 Operatori in limbajul de ansamblare
Limbajul de ansamblare dispune de un set de operatori cu care se pot forma expresii aritmetice si logice. Aceste
expresii sunt evaluate la momentul ansamblarii, producand constante numerice. Este esential sa deosebim instructiunile
executabile (codul masina) de operatiile care se fac la momentul ansamblarii.
3.7.1.Operatori aritmetici si logici
Operatorii aritmetici sunt : + , - , * , / , mod (modulo) , shl (shift left), shr (shift right) . Primii 4 au semnificatii
evidente si opereaza numai cu cantitati intregi. Operatorul mod produce restul la impartire iar shl si shr provoaca
deplasarea la stanga sau la dreapta. De exemplu instructiunea : cod 3.60 este echivalenta cu cod 3.61. Similar in
directiva cod 3.62 se va lua la momentul ansamblarii expresia ($ - TAB)/2 , adica diferenta dintre contorul curent de
locatie si adresa TAB impartita la 2.
Operatorii logice NOT, AND, OR si XOR au semnificatii evidente. Toti acesti operatori accepta drept operanzi
constante intregi; operatiile respective se fac la nivel de bit. Ei nu trebuie confundati cu instructiunile executabile cu
acelasi nume. In instructiunea cod 3.63 registrul AL va fi incarcat cu constanta 101000B.
3.7.2.Operatori relationali
Operatorii relationali sunt EQ (=), NE (!=), LT (<), LE (>), GT (<=), GE (>=) cu semnificatii evidente. Acestia intorc
valori logice codificate ca 0 sau ca secvente de 1 pe un numar corespunzator de biti. In urma definitiilor cod 3.64.
Variabilele x,y,z vor fi initializate cu constantele 0, 0FFFFH, OFFFFFFFFH. Operatorii relationali sunt utilizati in special in
directivele de ansamblare conditionala.
3.7.3.Operatorul de atribuire =
Defineste constante numerice fiind similar cu directiva EQU, dar permite redefinirea simbolurilor utilizate. O
definire de forma : cod 3.65 este semnalata ca eroare, dar secventa cod 3.66 este corecta. Acest operator este utilizat in
special in definitiile de macroinstructiuni.
3.7.4.Operatori care intorc valori
Acesti operatori se aplica unor entitati ale programului in limbaj de ansamblare (variabile,etichete) intorcand valori
asociate acestora.
-operatorul SEG, se aplica atat variabilelor cat si etichetelor si furnizeaza adresa de segment asociata variabilei
respective. cod 3.66. Daca var este o variabila atunci putem scrie secventa cod 3.67.
-operatorul OFFSET, este similar cu SEG furnizand offset-ul asociat variabilei sau etichetei : cod 3.68.
-operatorul THIS, creaza un operand care are o adresa si un offset identice cu contorul curent de locatie. Forma generala
a operandului este cod 3.69, in care tip poate fi byte, word , dword, qword, dbyte pentru definitii de date respectiv NEAR,
FAR pentru etichete. Operatorul THIS se utilizeaza de obicei cu directiva EQU. De exemplu definitia constantei simbolice:
cod 3.70 este echivalenta cu definitia unei etichete cod 3.71.
-operatorul TYPE, se aplica variabilelor si etichetelor intorcand tipul acestora. Pentru variabile intoarce valorile 1,2,4,8,10 ,
pentru variabile simple (definite cu directivele DB,DW,DQ,DT), iar pentru structuri intoarce numarul de octeti pe care este
memorata structura respectiva. Pentru etichete intoarce tipul etichetei (NEAR SAU FAR).
-operatorul LENGTH, se aplica numai variabilelor si intoarce numarul de elemente definite in variabila respectiva. De
exemplu, in cod 3.72 expresia LENGTH A are valoarea 100.
-operatorul SIZE, se aplica numai variabilelor si intoarce dimensiunea in octeti a variabilei respective. In definitia de mai
sus expresia Size A are valoarea 200. Pentru o variabila oarecare var are loc intotdeauna identitatea : cod 3.73.
Acesti operatori sunt utili la prelucrarea tablourilor. Secventa urmatoarea nu depinde de tipul de baza al tabloului
TAB si nici de dimensiunea sa : cod 3.74. Daca inlocuim tipul de baza al tabloului TAB cu o structura de tip PERSOANA,
modificand dimensiunea : cod 3.75, partea de program nu trebuie modificata deoarece ansamblorul va evalua corect
expresiile, length tab si type tab.
3.7.5.Operatorul PTR
Acest operator se aplica atat variabilelor cat si etichetelor avand ca efect schimbarea tipului variabilei sau a
etichetei respective. Este obligatoriu in cazul in care se folosesc referinte anonime la memorie din care nu se poate
deduce tipul operandului : cod 3.76.
3.8.Directive de ansamblare aditionale
Permit ignorarea unor optiuni din textul sursa, functie de o conditie care se poate evalua la ansamblare. Directiva
de baza este if cu forma generala cod 3.77. Daca expresia din directiva IF este adevarata (diferita de 0) se ansambleaza
partea de program dintre if si else si se ignora portiunea dintre else si endif. Daca expresia este falsa se ansambleaza
portiunea dintre Else si end si se ignora dintre if si else. Ramura else este optionala.
Aceste operatii se petrec la momentul ansamblarii, permitand intretinerea comoda a unui program de dimensiuni
mari sau chiar mentinerea in acelasi text sursa a mai multor variabile de program executabile. Sa presupunem ca dorim
un program care sa poata fi configurat rapid pentru modele de date mici, respectiv mari. Acesta presupune ca orice
secventa de instructiuni care defineste, citeste sau scrie o adresa sa fie inclusa in directivele de ansamblare conditionata
(cod 3.78).
Exista si directivele IFDEF/ENDIF si IFNDEF/ENDIF care testeaza daca un simbol este definit sau nu si
directivele IFB/ENDIF respectiv IFNB/ENDIF care testeaza daca un simbol este vid sau nevid. Acestea din urma vor fi
utilizate la definirea macroinstructiunilor.
In expresiile care apar in directivele de tip IF se utilizeaza de obicei operatori logici si operationali.
Sa consideram un exemplu de program sursa multifunctionala. Se doreste scrierea unei secvente care sa calculeze un
acumulator, suma elementelor unui tablou A (de octeti sau de cuvinte) definit in segmentul curent de date, ca suma pe
octeti si pe cuvinte indiferent de tipul si dimensiunea tabloului. Tipul sumei este dictat de constantele simbolice SUM_B si
SUM_W : cod 3.79. Se observa ca nu putem initializa contorul CX cu expresia length a deoarece numarul de iteratii este
dictat de modul de calcul al sumei si de tipul tabloului. Ca atare, luam dimensiunea in octeti a tabloului pe care o
imparatim la 2 in cazul sumei de cuvant. Calculul sumei sunt evidente. Apare o problema speciala in urmatoarea situatie :
se cere suma pe cuvant, iar tabloul definit ca tabloul de octeti are un numar impar de elemente. In aceasta situatie este
evident ca ultimul octet nu este considerat in bucla de sumare. Ca atare el este adaugat explicit la sfarsit. De remarcat
formularea situatiei de mai sus in termenii conditiei logice de la ultima directiva IF :
-SUM_W codifica cererea de calcul a sumei la nivel de cuvant.
-TYPE A EQ 1 codifica situatia in care tabloul A este definit la nivel de octet.
-((SIZE A) MOD 2)EQ 1 codifica situatia in care tabloul are un numar impar de octeti.

Capitolul 4.Macroinstructiuni
4.1.Scopul macroinstructiunilor.Definire si expandare.
Macroinstructiunile permit programatorului sa defineasca simbolic secvente de program (instructiuni, definitii de
date, directive etc) asociate cu un nume. Folosind numele macroinstructiunii in program se va genera intreaga secventa
de programe. In esenta este vorba de un proces de substitutie (expandarea) in textul programului sursa care se petrece
inainte de ansamblarea programului. Un asamblor care dispune de macroinstructiuni se numeste macroansamblor. Sunt
2 etape de lucru cu macroinstructiuni :
-definirea macroinstructiunilor si utilizare lor. Utilizarea se mai numeste invocare sau chiar apel de macroinstructiune, dar
ultima denumire poate produce confuzie, termenul fiind folosit in cazul procedurilor.
Spre deosebire de proceduri, macroinstructiunile sunt expandate la fiecare utilizare, deci programul nu se micsoreaza.
Avantajul este ca textul sursa scris de programator devine mai clar si mai scurt. Macroinstructiunile pot fi cu parametri sau
fara parametri. Din punct de vedere sintactic, ele sunt asemanatoare cu directivele de tip Define din limbajele de nivel
inalt, dispunand in general de mecanisme mai evoluate decat acestea.
Definitia unei macroinstructiuni fara parametri se face in forma generala : cod 3.80.
-invocarea consta prin scrierea din codul sursa a numelui macroinstructiunii. Macroinstructiunea unit_ds_es este definita
in fisierul io.h prin : cod 3.81.
Macroinstructiunea exit_dos este definita prin cod 3.82.
O pereche de macroinstructiuni care salveaza si refac registrele generale poate fi conceputa astfel : cod 3.83.
Putem utiliza aceste macroinstructiuni la intrarea si la iesirea dintr-o procedura : cod 3.84.
Din punct de vedere simbolic este ca si cum setul de instructiuni al masinii ar fi fost extins cu 2 noi instructiuni : save si
rest.
4.2.Macroinstructiuni cu parametri
Macroinstructiunile importante sunt cele cu parametri. Definitia unei macroinstructiuni cu parametri are forma generala :
cod 3.85, in care p1,p2,..,pn sunt identificatori care specifica parametrii formali apelului (invocarea) unei macroinstructiuni
cu parametri se face prin specificarea numelui, formata de o lista de parametri actuali : cod 3.86.
La expandarea macroinstructiunii, pe langa expandarea propriu zisa se va inlocui fiecare parametru formal cu parametrul
actual respectiv. Sa consideram cateva exemple. Apelurile de functii DOS presupun numarul functiei in registrul AH.
Putem defini deci o macroinstructiune de forma : cod 3.87. si putem invoca macroinstructiunea prin linii de forma cod
3.88.
Similar, pentru deschiderea unui fisier disc pentru citire (operatie realizata prin functia DOS 3DH) putem scrie o
macroinstructiune de forma cod 3.89 in care fname contine numele fisierului si hand este o variabila de tipul word in care
se depune un indicator catre fisierul deschis. Parametru 0C0H codifica modul de acces. Se observa utilizarea
macrointructiunii dosint in definitia lui o_read. Citirea din fisier poate fi codificata intr-o macroinstructiune de forma : cod
3.90 in care hand este indicatorul catre fisierul anterior deschis, nr este numarul de octeti care se citeste, iar buf este
adresa unei zone de memorie in care se vor depunde datele citite.
Inchiderea unui fisier deschis se poate face cu macroinstructiune de forma : cod 3.91.
Macroinstructiunea de mai sus este definita in io.h. Ne propunem acum sa scriem un program executabil care sa afiseze
la consola un fisier text. Beneficiem de macroinstructiunile definite din io.h. cod 3.92.
In zona de date se rezerva spatiu pentru numele fisierului, pentru indicator (handler) si pentru bufer de citire de 1024 de
octeti. Reamintim ca operatiile cu perifericile sunt in esenta transferuri intre periferice si memorie. Programul afiseaza un
mesaj la consola dupa care citeste un nume de fisier. Se face apoi operatia de deschidere a fisierului specificata. Toate
functiile DOS de lucru cu fisiere introc CF=1 in caz de eroare. O eroare tipica la operatia de deschidere pentru citire este
un nume eronat de fisier. Se testeaza deci CF si in caz de eroare se afiseaza un mesaj adecvat si se iese in DOS.
Se trece acum la o bucla de citire-afisare. Functia de citire intoarce in AX numarul de octeti efectivi cititi(care este <= cu
cel cerut). Dorim sa afisam numai ce s-a citit efectiv, asa ca punem terminatorul 0 in buffer dupa ultimul octet efectiv citit.
Este posibil ca la ultima iteratie sa se citeasca 0 octeti. Se afiseaza la consola bufferul respectiv (cu macroinstructiunea
puts).
Daca numarul de octeti efectiv cititi este mai mic strict decat cel cerut (1024) inseamna ca s-a ajuns la sfarsitul fisierului si
bucla se termina. In caz contrar se reia o noua citire din fisier. In final, se inchide fisierul si se iese in DOS. Acest exemplu
ilustreaza foarte bine avantajele macroinstructiunilor. O actiune destul de laborioasa in limbaj de ansamblare (afisare
fisiertext) a putut fi codificata prin cateva linii de program sursa (e drept ca aproape toate sunt invocari de
macroinstructiuni). Daca reusim sa concepem un set de macroinstructiuni adecvat unei probleme (in cazul de fata
interfata cu sistemul DOS) scrierea programelor devine foarte comoda.
In cazul scrierii unui program similar, care sa realizeze copierea unui fisier disc in alt fisier, se pot cuprinde
macroinstructiunile : cod 3.93, care deschid un fisier pentru scriere, respectiv scriu un fisier. Parametrii sunt asemanatori
cu cei din macroinstructiunile o_read si f_read. Bucla de citire din fisier-afisare din exemplul anterior se inlocuieste cu o
bucla de citire din fisier sursa-scriere in fisier destinatie.
In unele situatii substitutia parametrilor formali cu cei actuali poate ridica unele probleme. Sa presupunem ca un
programator care invata limbajul ASM nu a ajuns la instructiunea XCHG, ci are cunostinta doar de instructiunile
PUSH,POP,MOV. El isi propune sa scrie o macroinstructiune care sa interschimbe 2 cantitati de 16 biti : cod 3.94.
Aparent, totul e in ordine. Totusi pot aparea situatii nedorite ca in secventa : cod 3.95. Aceasta invocare de
macroinstructiuni se expandeaza in : cod 3.96 si este evident ca registrul AX nu se modifica. Se poate insa si mai rau ca
in secventa cod 3.97 care se expandeaza in cod 3.98.
Pericolul apare deci in situatiile in care parametrii actuali intra in conflict cu anumite variabile sau registre care sunt
folosite in interiorul macroinstructiunii. Aceste trebuie evident evitate.
Utilizarea foarte mare a macroinstructiunilor devine evidenta la secvente de apel ale procedurilor cu parametri (in acest
caz se vorbeste de macroinstructiune de apel). Apelurile de functii sistem modificate anterior sunt cazuri particulare de
macroinstructiuni de apel.
Procedurile cu parametri utilizeaza diverse tehnici de transmitere a parametrilor. Parametrii trebuie clasati in anumite
registre sau in stiva, intr-o ordine specificata. Aceste detalii de apel sunt greu de tinut mine si nici nu intereseaza pe cel
care apeleaza procedura. Putem insa dezvolta macroinstructiuni care sa ascunda aceste detalii.
Sa consideram o procedura cu numele puts_proc, care afiseaza un sir de caractere (terminat cu 0). Adresa sirului
(offestul in cadrul segmentului curent) adresat prin DS se specifica in registrul SI. O macroinstructiune de apel se poate
scrie in forma : cod 3.99.
Ca parametru actual se poate utiliza orice operand compatibil cu instructiunea LEA, astfel daca exista definitia de date :
cod 3.100 se pot utiliza formele de invocare : cod 3.101.
In al doilea caz nu putem scrie puts BX pentru ca acest apel ar conduce la o expandare de forma lea SI,BX , ceea ce
este o eroare de sintaxa. Forma puts [BX] se expandeaza in LEA SI,[BX] care este corecta.
4.3. Macroinstructiuni repetitive
Aceste macroinstructiuni sunt predefinite, deci nu trebuie definite de utilizator. Scopul lor este de a genera secvente
repetate de program.
- Macroinstructiunea REPT (repeta) :
Forma generala de invocare este : Cod 3.114 in care n este o constanta intreaga. Efectul este repetarea
corpului macroinstructiunii de n ori. Secventa anterioara de generare a 3 mesaje s-ar putea scrie acum - Cod
3.115
Iata o secventa care genereaza un sir de caractere cu litere de la ‘A’ la ‘Z’ – Cod 3.116
- Macroinstructiunea IRP (repeta nedefinit) :
Forma generala de invocare este : Cod 3.117 in care p_formal este un parametru formal , iar lista_par_act este o lista de
parametri actuali separate prin virgule. Efectul este repetarea corpului macroinstructiunii de atatea ori cate elemente
contine lista de parametri actuali.
La fiecare repetare se substituie parametrul formal cu cate un parametru actual. Invocarea ( Cod 3.118 ) se va expanda
in Cod 3.119.
4.4. Operatori specifici
Exista o serie de operatori specifici macroinstructiunilor. Acestia controleaza in principal substitutia parametrilor actuali
fiind utili in special in macroinstructiunile repetitive.
4.4.1. Operatorul de substituire si de concatenare ( & )
Acest operator aplicat unui parametru formal realizeaza substitutia si ( eventual ) concatenarea sa cu un text fix sau un alt
parametru formal. Este necesar in contextul in care un parametru formal ar fi interpretat ca un simbol ( de exemplu, intr-un
sir constant de caractere sau intr-un identificator ) .
Sa presupunem ca dorim sa definim automat liniile de program : Cod 3.120
Vom apela , evident, la macroinstructiunea IRP. O prima incercare ar fi : Cod 3.121. Ceea ce este incorect
deoarece se va produce aceeasi linie de program. Prima aparitie a parametrului x nu poate fi distinsa de simbolul
mesaj_x, iar a doua nu poate fi distinsa de sirul constant text x . Aici intervine operator & , definitia corecta fiind : Cod
3.122 in care primul caz se concateneaza textul fix mesaj & cu parametrul formal x , iar in al doilea se substituie
parametrul formal x chiar daca apare intr-un sir constant de caractere.
Sa consideram un exemplu inrudit , codificat prin macroinstructiunea urmatoare : Cod 3.123
Aici e necesara concatenarea a doi parametri formali ( fix si x ) ; fiecaruia I se aplica operatorul & la stanga sau la dreapta
, dupa locul in care are loc concatenarea. Un apel de forma : Cod 3.124 va produce acelasi text ca macroinstructiunea
IRP de mai sus.
4.4.2. Operatorii de literalizare sir/character (<>,!)
Operatorul de literalizare sir ( <> ) se utilizeaza atunci cand se doreste ca un text in care apar eventualii
separatori ( spatii albe, virgule etc. ) sa fie considerat ca un unic parametru ( sa fie literalizat ) . Operatorul se utilizeaza
practice in definitii cat si in invocarile macroinstructiunii.
De exemplu, in definitia : Cod 3.125 dorim ca parametrul x de la nivelul exterior sa fie transmis ca atare catre
macroinstructiunea IRP. Invocarea se va face in forma : Cod 3.126 ceea ce are ca effect transmiterea listei ‘A’, ‘B’, ‘C’, ‘D’
ca un unic parametru.
Operatorul de literalizare character ( ! ) se aplica unui singur character , efectul fiind de a trata acel character ca
un character obisnuit ( fara a-l interpreta) . Se utilizeaza impreuna cu caractere care au semnificatii speciale in
macroinstructiune.
Sa consideram o macroinstructiune care defineste mesaje de eroare : Cod 3.127
O invocare de forma : Cod 3.128 se va expanda in Cod 3.129
Daca dorim insa sa generam un mesaj de forma ‘par_1 > par_2’ , intram in conflict cu semnificatia speciala a
caracterului > care intra in componenta operatorului de literalizare . Solutia este sa folosim operatorul ! si sa scriem : Cod
3.130 , ceea ce va genera mesajul correct : Cod 3.131
4.4.3. Operatorul de evaluare expresii ( % )
Acest operator se aplica unei expresii oarecare , efectul fiind evaluarea acelei expresii . Dintr-un anumit punct de vedere,
operatorul % este inversul operatorilor de literalizare .
Sa consideram macroinstructiunea : Cod 3.132 care genereaza date.
O invocare de forma : Cod 3.133 va produce liniile de program : Cod 3.134 .
4.5. Invocarea recursiva de macroinstructiuni
O macroinstructiune se poate invoca recursive , adica pe ea insasi. Ca si la procedurile recursive, cel mai important lucru
este oprirea recursivitatii , care se poate face cu directivele de asamblare conditionata IFB sau IFNB .
Sa consideram o macroinstructiune de salvare de registre in stiva . Vrem ca aceasta sa permita un numar variabil de
parametri . Solutia recursive este sa fixam un numar maximal de parametri si sa testam explicit daca primul parametru
este vid : Cod 3.135 .
Daca primul parametru formal nu este vid, se genereaza instructiunea push si apoi se invoca aceeasi macroinstructiune
cu restul de parametri . Aceasta forma poate fi folosita cu un numar oarecare de registre de la 1 la 6 , de exemplu : Cod
3.136 .
O alta varianta este cea repetitiva : Cod 3.137
Invocarea necesita insa operatorul de literalizare : Cod 3.138
4.6. Definirea macroinstructiunilor in macroinstructiuni
Se poate spune ca macroinstructiunile automatizeaza oarecum procesul de definire a datelor si a instructiunilor.
Mai mult decat atat, chiar definirea macroinstructiunilor se poate face prin intermediul altor macroinstructiuni.
Sa consideram un asemenea caz in cazul procesorului x8086 instructiunile de deplasare si de rotatie cu un numar de biti
mai mic sau egal cu 3 se executa mai rapid ca secvente de rotatie de cate un bit in comparatie cu instructiunile care
utilizeaza registrul CL . Dorim sa scriem cate o macroinstructiune pentru cele 8 instructiuni de deplasare si rotatie , fiecare
cu 2 parametri : sursa si numarul de biti , care sa se expandeze in secventa ultima ca timp de executie.
De exemplu, o invocare de forma : Cod 3.139 sa se expandeze in secventa : Cod 3.140 iar o invocare de forma Cod
3.141 ,in secventa Cod 3.142
Dorim ca generarea macroinstructiunilor ( avand numele de forma m_xxx , unde xxx este numele instructiunii
corespunzatoare ) sa fie facuta automat.
Incepem prin a defini o macroinstructiune care primeste ( in parametrul formal operation ) numele instructiunii respective
si genereaza macroinstructiunea corespunzatoare : Cod 3.143 .
La o invocare de forma : Cod 3.144 , se va genera automat macroinstructiunea m_shl , conform definitiei echivalente :
Cod 3.145 ,adica exact ce ne-am propus . Generam acum toate cele 8 macroinstructiuni , observand ca numele
operatiilor ( instructiunilor ) respective se compun din secvente RO , RC , SH, SA , la care se adauga sufixele R sau L .
Exploatam acest fapt prin doua macroinstructiuni repetitive : Cod 3.146 .
Aceasta secventa va defini macroinstructiunile m_ror, m_rol , m_rcr, m_rcl ,m_shr, m_shl , m_sar, m_sal .

4.7.1. Macroinstructiuni care se autotransforma ( se redefinesc ) in procedure


Un dezavantaj al utilizarii intensive a macroinstructiunilor , este consumul de memorie : daca invocam de 100 de
ori o macroinstructiune , textul respective se va duplica de 100 de ori . Acest lucru nu deranjeaza in situatia in care textul
respectiv trebuia oricum scris ( de exemplu, in secventele de apel ale procedurilor ) . Sunt situatii in care corpul
macroinstructiunii reprezinta o secventa oarecare de instructiuni . Dorim ca invocarile repetate sa nu conduca la repetarea
corpului macroinstructiunii , ci la apeluri ale unei proceduri , acest lucru fiind transparent pentru utilizator. Solutia este
urmatoarea : Cod 3.147 .
Se inscrie corpul macroinstructiunii intr-o procedura cu numele subr si se genereaza un apel ale acestei proceduri , urmat
de un salt peste procedura respectiva . Se redefineste apoi macroinstructiunea macsub in asa fel incat sa se genereze
doar apeluri ale procedurii subr. La prima invocare a macroinstructiunii macsub , se va genera textul : Cod 3.148 , la
urmatoarele invocari, se va genera doar textul : Cod 3.149 .
Acest exemplu , arata ca intr-o macroinstructiune, putem chiar redefine aceeasi macroinstructiune
4.7.2.Macroinstructiuni care genereaza atat date cat si cod
Pe parcursul cursului s-a prezentat macroinstructiunea putsi care permite afisarea la momentul executiei a unor siruri
constante ,,imediate", fara a le defini explicit intr-un segment de date. Puteam descrie 3.150 fara sa ne punem problema
unde se memoreaza sirul constant respectiv. Pentru afisarea propriu-zisa utilizam procedura puts_proc din fisierul io.asm,
care primeste in DS:SI adresa de memorie a unui sir terminat cu 0. Problema care se pune este ca definitia sirului in
memorie trebuie facuta chiar in macroinstructiunea putsi. Vom beneficia de faptul ca directivele simplificate de definire a
segmentelor (in speta .code si .data) pot alterna in cuprinsul unui program. Definitia macroinstructiunii este urmatoarea ->
3.151. Se comuta in segmentul de date si se defineste sirul transmis prin parametrul formal X, la care se adauga
terminatorul 0. Identificatorul string se declara local pentru a nu fi duplicat in apeluri succesive. Se comuta apoi pe
segmentul de cod si se genereaza secventa de apel a procedurii puts_proc, cu salvarea si restaurarea registrului SI. Un
apel de forma : cod 3.152 se va expanda intr-o secventa de forma cod 3.153. In mod similar a fost introdusa o
macroinstructiune geti care citeste un intreg cu semn pe 16 biti de la consola,intorcand valoarea citita in registrul AX.
Problema care apare este ca de la consola se pot citi date numai la nivel de caractere. Presupunem ca dispunem de o
procedura gets_proc care citeste un numar limitat de caractere de la consola. Parametrii acestei proceduri sunt : adresa
(near) unde se depun caracterele citite transmise prin SI si numarul maxim de caractere citite transmis in CX. Citirea
inceteaza fie la apasarea ENTER, fie la atingerea numarul maxim de caractere, iar dupa ultimul caracter se depunde
terminatorul 0. Daca CX=0 atunci se poate introduce un numar nelimitat de caractere.
Dupa ce s-au citit caracterele care formeaza numarul, acestea trebuie convertite de la sir de caractere ASCII la un intreg
cu semn. Aceasta conversie se realizeaza cu o procedura atoi_proc, care primeste in SI adresa sirului de caractere si
introarce in AX valoarea calculata.
Macroinstructiunea geti se defineste : cod 3.154.
*Manipularea datelor
Procesoarele ARM reprezinta o arhitectura Load/Store, ceea ce inseamna ca toate calculele (adunare, inmultire etc) sunt
efectuate cu date retinute in registre, nu in memorie. Data trebuie mutata in registru inainte de a se opera cu ea.
Instructiunile de transfer includ instructiuni pentru incarcarea constantelor intr-un registru, incarcarea/salvarea din slash in
memorie si operatii cu stiva.
5.1.Incarcarea constantelor in registri
Instructiunile MOV si MVN includ o constanta chiar in reprezentarea lor(corpul instructiunii). Astfel de constante se
numesc constante imediate din moment ce ele sunt valide la extragerea instructiunii nefiind nevoie de incarcarea
constantei dintr-o locatie separata de memorie.
Instructiunea MOV copie constanta in registru; instructiunea MVN reprezinta un MOV Not, copiind inversul constantei in
registru.
exemplu :
MOV R1,#100 ; in R1 va intra 100 .. R1<-100
MVN R1,#100 ; in R1 va intra ~100 sau R1<- -101
Constantele sunt intotdeauna precedate de semnul #. Implicit ele sunt scrise in zecimal; Constantele zecimale trebuie
precedate de 0x ca in exemplul :
MOV R1,#0xFB
Desi instructiunea MOV suporta constantele pozitive, in cazul folosirii unor constante negative asamblorul va face
conversia in instructiunea MVN echivalenta. Spre exemplu :
MOV R1,#-100 <----> MVN R1,#99
Din nefericire nici MOV si nici MVN nu suporta valori de 32 biti din moment ce numai un numar mai mic de 32 de biti din
instructiune sunt folositi pentru a retine constante cuprinse intre 0 si 255 si alte cateva valori. Cand este necesara o
valoarea diferita, singura solutie este de a memora constanta intr-o locatie separata de memorie si de a o incarca folosind
o instructiune de referire la memorie.
Vestea buna este ca exista pseudo operatii care functioneaza pentru orice constanta pana la 32 de biti. Se va scrie o
instructiune ARM (exceptand = care inlocuieste semnul #) iar ansamblorul va face conversia instructiunii cat mai eficienta.
LDR R1,=10 ; Ansamblorul inlocuieste aceeasta instructiune cu un MOV R1,#10
LDR R1,=-15 ; Ansamblorul inlocuieste cu MVN R1,#14
LDR R1,=-127435 ; Ansamblorul inlocuieste aceasta printr-o instructiune de referire la memorie care incarca o constanta
dintr-o locatie.
5.2.Incarcarea unei date din memorie intr-un registru
Instructiunea LDR este folosita pentru incarcarea unei date din memorie intr-un registru. Instructiunea normala LDR copie
o valoare pe 32 biti din memorie in registru. Cand se incarca 8/16 biti de date intr-un registru pe 32 biti, operandul este
aliniat la dreapta in interiorul registrului iar bitii cei mai importanti (MSB) sunt scrisi in corcondanta cu tipul datei (cu/fara
semn). Fig 5.1 si fig 5.2.
Operanzii fara semn mai mici de 32 biti trebuie sa umple pozitiile MSB cu 0. Asta asigura interpretarea fara semn a
registrului, la fel ca si valoarea incarcata in memorie. Spre exemplu, instructiunea de incarcare a unui registru cu o
jumatate de cuvant fara semn in memorie (LDRH) folosita pentru intregii fara semn pe 16 biti, ea incarca o jumatate de
cuvant pe 16 biti din memorie in cei mai putin semnificativi (LSB) in registru si umple cu 0 restul de 16 biti. La fel,
incarcarea unui registru cu 1 byte fara semn din memorie (LDRB) este folosita pentru a incarca 8 biti din memorie fara
semn in cei mai putini semnificativ 8 biti din registru, iar restul de 24 biti sunt scrisi cu 0.
Operanzii cu semn mai mici de 32 biti trebuie sa umple bitii MSB . (fig 5.2) cu bitul lor de semn. Extensia semnului este
folosita deoarece procesoarele ARM,ca majoritatea procesoarelor moderne , folosesc reprezentarea coplement fata de 2
a numerelor cu semn. Spre exemplu, incarcarea unui registru cu o jumatate de cuvant cu semn din memorie (LDRSH)
este folosita pentru intregii cu semn pe 16 biti. Aceasta instructiune umple 16 biti ramasi din registru cu 16 copii ale bitului
de semn (bitul 15) a operandului din memorie. La fel, incarcarea din memorie a unui byte cu semn (LDRSB) este folosita
pentru intregii cu semn pe 8 biti iar restul 24 de biti sunt copiati din bitul 7 a operandului din memorie.
Instructiunea LDRD incarca o pereche de registri cu 2 jumatati a unui dublu cuvant pe 64 de biti din memorie. Valoarea pe
64 de biti este memorata in format little indian cu cea mai putin semnificativa jumatate (bitii 0-31) memorati in adresele pe
octet de la N pana la N+3 , iar cea mai semnificativa jumatate (bitii 32-63) returnati in adresele pe octet de la N+4 pana la
N+7. Cea mai putin semnificativa jumatate este returnata in registrul din stanga a instructiunii iar cea mai semnificativa
jumatate este returnata in registrul urmator.
N+7 N+4 N+3 N
7 6 5 4 3 2 1 0

5.3.Memorarea datelor din registrii in memorie


Instructiunea LDR pe langa faptul ca trebuie sa suporte o varietate de operanzi de diferite marimi, mai trebuie sa lucreze
si cu operenzi cu/fara semn. Instructiunea STR normala copie 32 de biti in registru intr-o locatie de memorie pe 32 de biti.
Instructiunile STRB si STRH sunt folosite atunci cand destinatia/locatia de memorie este pe 8/16 biti si copie numai cei
mai putin semnificativi biti din registru in memorie. Instructiunea STRD copie 64 de biti dintr-o pereche de registri intr-un
dublu cuvant pe 64 de biti din memorie si opereaza invers ca instructiunea LDRD.
Cand se scrie un byte sau o jumatate de cuvant in memorie toti 4 octeti ai registrului sunt plasati pe magistrala de date a
memoriei de 32 biti dar numai unul sau doi din acesti bytes sunt activati pentru scriere pe parcursul ciclului de scriere.
Spre exemplu, pentru a scrie o jumatate de cuvant in memorie incepand cu adresa 104 sunt plasati pe magistrala de date
4 octeti corespunzand cu adresele 104,105,106,107. Fiecare byte(octet) de pe magistrala de date a memoriei are propriul
lui bit de activare. Pe parcursul ciclului de scriere in memorie, semnalele de scriere a adresei pentru cadrele 106 si 107
sunt dezactivate si sunt scrise doar adresele 104 si 105.
5.4. Conversia unei instrucțiuni simple C în limbaj de asamblare ARM
Instrucțiunile LDR și STR sunt singurele instrucțiuni care fac referință la memorie și apar frecvent în cod. Vom face
un exemplu simplu despre cum este translatat un cod C , cum ar fi dst=0 sau dst=SRC; în limbaj de asamblare ARM.
Tabelul 5.3(variable<-constant) prezintă soluțiile care acoperă operanzii pe 8,16,32 și 64 de biți cu semn sau fără
semn.(slide 1-15)
5.5 Calcularea adreselor de memorie
Pe durata fazei de execuție a instrucțiunii(exemplu LDR R0, data), aceasta trebuie să dea adresa operandului
data din memorie. Reținerea adresei pentru a o conserva pe 32 de biți în reprezentarea instrucțiunii ar însemna ca
instrucțiunile LDR și STR să fie mai mari de 32 de biți. Pentru a conserva spațiul de memorie și timpul de extragere a
instrucțiunii, aceste instrucțiuni specifică cum ar trebui calculată adresa dintr-un număr de biți mai mic. Asamblorul
convertește automat referința la variabila data într-o referință relativă la PC, în care adresa este calculată ca sumă a
PC(registrul R15, care este echivalent cu PC) și o constantă mai mică reținută în instrucțiune. Constanta este cunoscută
sub numele de deplasament și reprezintă diferența dintre adresa instrucțiunii și adresa datei. Ea este pe 13 biți și poate
avea valori cuprinse între +/- 4095.
Adresarea relativă la PC este un caz special al modului de adresare ARM cunoscut sub denumirea de adresare cu
offset. Ea prezintă 4 opțiuni de bază pentru calcularea adresei(tabelul 5.4-offset addressing).
Este important de reținut că câmpul operandului dintr-o instrucțiune care accesează memoria este restricționat în
folosirea expresiilor a căror valoare este o constantă cum ar fi adresele reprezentate prin numele variabilelor sau una din
cele 4 opțiuni prezentate în tabelul 5.4 .Spre exemplu, asamblorul va rejecta instrucțiunea LDR R0,data[k] sau folosirea
de nume de variabile în interiorul adresei închise cu [ ]. Exemplu: LDR R0, [R0+k].
Figura 5.4 prezintă calcularea adresei folosind un registru, un deplasator și o constantă mai mică de 32 de biți
pentru generarea adresei unui operand.
Alte două moduri de adresare sunt generate de adresarea cu offset. Acestea sunt folosite în procesarea și rularea de
date: adresare preindexată(în care se folosesc [] ! ) și adresarea postindexată(în care se folosesc [ ])
5.6 Exemple pentru adresarea memoriei
Când o instrucțiune accesează o dată din memorie, pot apărea 2 situații: adresa dată e constantă sau adresa
trebuie calculată în timpul rulării.
În C, variabilele a căror adrese este constantă sunt variabile globale(în afara funcțiilor) sau în interiorul funcțiilor, dar
declarate statice.
5.6.1. Translatarea în assembler a expresiilor C cu pointeri
În C, pointerii sunt folosiți pentru a reține adrese ale unor locații din memorie. Ei sunt inițializați de obicei prin
încărcarea lor cu rezultatul operatorului &(adresa variabilei) aplicat unei variabile. Exemplu :
int 8_t *ptr2byte; //pointer pe 32 de biți care memoreazp adresa unei variabile de tip byte
int 8_t data;
ptr 2byte=&data; //se memorează adresa variabilei data-constantă- în pointer
Această valoare a lui &data este o constantă. În asamblare ARM, există o instrucțiune pentru a obține această
constantă:
ADR R0,data ;R0 <- data
STR R0, ptr2byte ;ptr2byte <- R0
Câmpul operatorului sursă al instrucțiunii ADR poate folosi orice referire la memorie, la fel ca instrucțiunea LDR sau
STR, dar aceasta nu face o referire la memorie, ea doar calculează adresa fără a provoca o scriere sau o citire din
memorie. Această adresă este calculată prin adunarea unui offset la valoarea curentă a PC-ului.
Când se face o dereferință a unui pointer se folosește conținutul acestuia ca și adresă a datei la care se face referință.
Să presupunem că ptr2byte a fost inițializat cu adresa lui data, putem memora valoarea 0 în variabila data printr-o
referință indirectă prin intermediul pointerului:
*ptr2byte=0(valoarea pointată); //se memorează 0 în byte-ul data
De reținut că se memorează valoarea 0 la o adresă dată de o variabilă și nu o constantă. De aceea, prin asamblarea
ARM ar trebui încărcat conținutul variabilei în registru , iar apoi folosit în interiorul a [ ] . Când se memorează valoarea 0 în
memorie
LDR R0, #0 ;se ia valoarea constantă 0
LDR R1,ptr2byte ;se memorează adresa reținută în ptr2byte
STRB R0, [R1] ;se memorează 0 în byte-ul de memorie.
Codul de mai sus foloseste instrucțiunea STRB deoarece pointerul a fost declarat să pointeze o dată pe 8 biți.
Pointerul este încărcat folosind o instrucțiune LDR deoarece adresa este întotdeauna pe 32 de biți. Este important de
înțeles cum funcționează aritmetica pointerilor:
int32_t array[100]; //un pointer de 100 de cuvinte pe 32 de biți
int32_t *ptr2word; //un pointer la un cuvânt pe 32 de biți
ptr2word = array; //inițializarea lui ptr2word cu adresa lui array[0]
Din moment ce ptr2word conține adresa lui array[0], atunci *ptr2word este o referință la conținutul lui array[0]. La fel
expresia ptr2word+1 este adresa lui array[1] și ptr2word+k este ardesa lui array[k].
Fiecare element al vectorului conține 4 octeți, dacă adresa lui array[0] este 1000, cea a lui array[1] este 1004. Asta
înseamnă că atunci când scriem ptr2word+1 adunăm 4 la adresa și nu 1. Spre exemplu, în declarația *(ptr2word+5)=0
echivalentă cu array[5]=0, aceasta se transformă în assembler ARM astfel:
LDR R0, #0 ;se memorează constanta 0
LDR R1, ptr2word ; se memorează adresa reținută în ptr2word
STR R0,[R1, #20] ; se memorează 0 la adresa 4 * 5 octeți mai sus în memorie
Dacă înlocuim constanta 5 cu variabila k, avem *(ptr2word+k)=0 ; array[k]=0. Asamblarea ARM devine:
LDR R0, #0 ;se citește constanta 0
LDR R1, ptr2word ; se citește adresa reținută în ptr2word
LDR R2, k ;se obține întregul memorat în k
STR R0,[R1, R2,LSL#2] ; se memorează la adresa 4 * k octeți dincolo de pointer, [....] <=> (R1+(R2<<2))
5.7. Instrucțiuni de lucru cu stiva
Procesorul implementează stiva prin alocarea unei zone de memorie și folosirea unui registru SP care reține adresa
următoarei locații în stiva ce poate fi accesată(TOS-top of stack). O operație PUSH scrie data în vârful stivei, iar o
operație POP citește data din vârful stivei(figura 5.5-Stack Operations).
Fecare PUSH scrie un set de regiștri în vârful stivei și decrementează SP(R13 cu 4 * nr_regiștrilor_scriși). Fiecare
POP încarcă un set de regiștri din vârful stivei și incrementează SP cu numărul regiștrilor încărcați.(tabelul 5.5)
tabelul 5.5
Instrucțiuni Operație Exemplu

PUSH{reglist} Regs -> mem[SP-4 * regs]; PUSH{R0, R1, R2}

[SP-=4* #regs];

POP{reglist} regs <-mem[SP]; POP{R1,R2,R6}

[SP+=4* #regs];

5.8. Instrucțiuni de procesare a datelor


Acestea sunt instrucțiuni aritmetice de deplasare și de manipulare a biților. Ele prezintă un număr de caracteristici
comune incluzând caracteristicile operatorilor și dacă acestea pot afecta sau nu flagurile(APSR - Application Program
Status Register). Aproape toate aceste instrucțiuni dau un rezultat din 2 operanzi sursă. Rezultatul și un operand sunt
reținuți în registre în timp ce al doilea operand poate fi o constantă(ex ADD R0, R1, #2 //R0=R1+2), un registru (ADD R0,
R1, R2 // R0=R1+R2), un registru care a fost shiftat(ADD R0, R1, R2, ASR #31//R0=R1+R2shiftat la dreapta cu 31 de
biți). Există 5 moduri în care al doilea operand poate fi deplasat(tabelul 5.6-Shift Codes).
5.8.1 Updatarea flag-urilor în APSR
Registrul APSR este folosit pentru a reține caracteristicile unei valori calculate și poate fi testat mai târziu. Pentru a se
lua decizia de salt, instrucțiunile LDR, STR , PUSH și POP nu modifică flag-urile. Restul instrucțiunilor pot modofica flag-
urile dacă se folosește litera S la sfârșitul mnemonicei instrucțiunii. Exemplu:
ADD R0, R0, #1 ;incrementează conținutul registrului R0, dar nu afectează flag-urile
ADDS R0, R0, #1 ;face același lucru, dar va afecta flag-urile N=1, dacă rezultatul<0, Z=1, rezultat=0 sau V=1
; pentru overflow în complement față de 2
Deși nu execută nicio operație aritmetică, versiunea MOVS a instrucțiunii MOV poate fi folosită pentru a reține
caracteristicile unei valori copiate de registru. Spre exemplu, MOV R1, R0 va copia content-ul registrului R0 în R1, dar nu
va modifica flag-urile, MOVS R1, R0 va updata flag-urile N și Z.
Tabelul 5.7(slide-ul 24 din ppt6) indică dacă se poate folosi caracteristica S la instrucțiune și care flag-uri pot fi
updatate în cazul folosirii.
5.8.2 Instrucțiuni aritmetice
Setul de instrucțiuni prezintă 4 operații aritmetice de bază pentru adunare, scădere, înmulțire și împărțire. În plus, mai
există și instrucțiunea RSP(Reserve Substract) folosită atunci când vrem să scădem o valoare a unui registru dintr-o
constantă. La fel mai există și instrucțiunea NEG(negative) folosită atunci când vrem să modificăm semnul unei valori
memorate în registru(tabelul 5.7).
Instrucțiunea ADD este folosită pentru adunările multiprecizie(mai mari de 32 de biți) la fel ca instrucțiunea
ADDS(figura 5.6 slide-ul 25 din ppt6).
Există mai multe instrucțiuni pentru înmulțire și 2 instrucțiuni pentru împărțire. Instrucțiunile SMULL și SMLAL
calculează un produs pe 64 de biți din operanzi pe 32 de biți cu semn. Instrucțiunile UMULL și UMLAL fac același lucru
pentru operanzi fără semn. Instrucțiunile MUL, MLA și MLS calculează produsul în simplă precizie(pe 32 de biți) pentru
operanzi cu/fără semn. Instrucțiunile MLA, MLS, UMLAL și SMLAL sunt folosite în aproximarea seriilor, iar instrucțiunea
MLS este folosită în calculul restului unei împărțiri(tabelul 5.8 slide-ul 26 din ppt6). Un exemplu de determinare a restului
unei împărțiri este prezentat în slide-ul 27 din ppt6.
5.8.3 Instrucțiuni pe bit
Aplicațiile Embeded procesează pachetele de informații care necesită instrucțiuni pentru manipularea biților. Setul de
instrucțiuni include următoarele operații de bază pe bit: NOT, AND, ORR, OR Exclusiv(tabelul 5.9 slide28 din ppt6).
Exemplu: slide 29 din ppt6.
5.8.4 Instrucțiuni de deplasare
Tabelul 5.10(slide 32 din ppt6) instrucțiunile de deplasare.Deși multe instrucțiuni(ADD, ORR şi altele) pot incorpora o
deplasare a unui operand, cantitatea deplasată este restricţionată la o constantă. Instrucţiunile de deplasare nu prezintă
această restricţie. Ele deplasează operandul N n cu numărul de biţi specifical de o constantă sau de conţinutul altui
registru(tabelul 5.10 slide32 din ppt6).
Instrucţiunile de deplasare diferă prin modul în care umplu golurile libere în urma deplasării(figura 5.7 slide33).
Deplasarea logică la stânga (LSL) şi deplasarea logică la dreapta(LSR) întotdeauna inserează 0. Deplasarea aritmetică la
dreapta(ASR) copie bitul de semn, astfel că rezultatul are acelaşi semn ca şi operandul. Rotirea la dreapta ROR execută
o rotire pe 32 de biţi şi scrie în bitul 31 valoarea conţinută în bitul 0. Rotirea la dreapta extinsă RRX execută o rotire pe 33
de biţi folosind şi bitul carry(c). O rotire pe 64 de biţi necesită 3 instrucţiuni(figura 5.8 slide-ul 34 din ppt6).
5.8.5 Instrucţiuni pentru manipularea câmpurilor de biţi
Aplicaţiile necesită şi instrucţiuni de extragere şi inserare a câmpurilor de biţi a căror dimensiune şi poziţie este fixă.
O instrucţiune poate insera 0 într-un câmp de biţi a unui registru, altă instrucţiune poate copia cei mai puţini semnificativi
biţi ai unui registru într-un câmp de biţi ai altui registru, sau poate extrage un câmp de biţi dintr-un registru şi îl poate
extinde la o valoare pe 32 de biţi cu/fără semn(tabelul 5.11 slide-ul 30 din ppt6).
Spre exemplu, figura 5.9(slide-ul 31 din ppt6) ilustrează cum o instrucţiune de manipulare a unui câmp de biţi poate
extrage 6 biţi fără semn(minutele) de la mijlocul unui pachet de 16 biţi(reprezentând timpul) şi îl poate înlocui cu valoarea
luată din alt registru.
Capitolul 6
În acest capitol sunt prezentate structurile de decizie, buclele şi apelurile de funcţii în asamblarea ARM. Majoritatea
programelor embeded sunt numiţi între C şi Asamblare. Programul principal şi majoritatea procedurilor de obicei sunt
implementate în C, iar o altă parte în asamblare, în special pentru optimizarea performanţelor.
6.1 Secvenţierea instrucţiunilor
În timpul funcţionării unui procesor, acesta repetă ciclul de extragere şi execuţie la infinit. Acest lucru se rezumă la o
execuţie liniară a instrucţiunilor citite din locaţii succesive a memoriei. Apar însă situaţii în care programul este forţat să
transfere controlul unei alte zone de memorii. În C, transferul controlului este creat prin instrucţiunile break, CONTINUE,
goto, apelurile de funcţii, return-urile şi implicit la sfârşitul buclelor: figura 6.1:
for ( ; ;) top_of_for:
{ ..... ........
if (a==5) .....
break; BEQ end_of_fpr
} B top_of_for
end_of_for:
6.2 Implementarea deciziilor
De obicei deciziile sunt rezultatul unei evaluări de expresii care poate fi adevărată sau falsă. Expresia (numită condiţie)
poate fi o simplă comparare a două valori,condiţiile compuse implicând mai multe operaţii altătuite din operatori booleane.
In asamblare, fiecare decizie prezintă doi paşi: o instrucţiune este folosită pentru setarea flag-urilor urmată de o altă
instrucţiune care foloseşte aceste flag-uri pentru a lua decizia de salt. În majoritatea cazurilor, prima instrucţiune compară
două valori prin calculul diferenţei lor. Acest rezultat nu este important, ci doar caracteristicile acestei comparaţii.
Spre exemplu, instrucţiunea CMP calculează diferenţa a două valori, dar salvează doar flag-urile, apoi prin verificarea
acestor flag-uri( folosind o instrucţiune de salt condiţionat)se poate lua o decizie cu privire la execuţia saltului.
Instrucţiunea CMP compară valoarea unui registru cu al doilea operand care poate fi o constantă, un alt registru sau un
registru deplasat.
Mai există şi instrucţiunea CMN(comparare negativă) care face comparaţii cu valoarea negată a unui registru.
Instrucţiunea TST este folosită pentru a testa dacă unul sau mai mulţi biţi dintr-un registru sunt diferiţi de 0 şi
instrucţiunea TEQ folosită pentru a testa dacă 2 valori sunt identice(tabelul 6.1 slide 2 din ppt7).
6.2.1 Instrucţiuni de salt condiţionat
Figura 6.1 include un exemplu al unei instrucţiuni de salt condiţionat(BEQ) care ia decizia de salt bazându-se pe
starea bitului Z din flag-uri. Dacă Z=1, rezultatul operaţiei este adevărat şi instrucţiunea iese din buclă, altfel se execută
următoarea instrucţiune după BEQ. Codul EQ(equal) din instrucţiunea în discuţie este numit cod de condiţie. Există 15
posibile condiţii prezentate în taelul 6.2(slide 4 din ppt7)
6.2.2 Implementarea declaraţiilor IF-Then şi IF-Then-Else
Să considerăm o declaraţie C If-Then prezentată în figura 6.2:
int32_t a,b;
if (a==0) b=1;
Din moment ce codul C foloseşte egalitatea pentru comparaţie, suntem tentaţi să folosim instrucţiunea BEQ, în acest
caz, trebuie să adăugăm un salt necondiţionat în codul de Assembler. O soluţie eficientă este folosirea instrucţiunii BNE
pentru a elimina acest salt necondiţionat
figura 6.2:
Ineficient Eficient

LDR R0, a LDR R0, 0

CMP R0, #0 CMP R0, #0

BEQ Then BNE EndIf

B Endif Then LDR R0, =1

Then LDR, R0,=1 STR R0,b

STR R0,b EndIf:......

Endif:...

Când se compară două valori, este important de ştiu că opusul lui ""mai mic decât" nu este "mai mare decât"; acesta
este "mai mare sau egal decât". Exemplele din figura 5.3 şi 5.4 prezintă acest lucru:
int32_t a,b;
if (a<100) b=1;
figura 6.3:
Incorect Corect

LDR R0, a LDR R0,a

CMP R0, #100 CMP R0, #100

BG L1 ;NUL BGE L1

LDR R0, =1 LDR R0, =1

STR R0,b STR R0, b

L1... L1:...
int32_t a,b;
if (a<100) b=1;
figura 6.4
Soluţia 1 Soluţia 2

LDR R0, a LDR R0,a

LDr R1,=100 CMP R0, #99

CMP R1, R0 BHI L1

BLS L1 LDR R0, =1

LD R0, =1 STR R0, b

STR R1, b L1:...

L1...

Cand se adauga o conditie else trebuie sa adaugam un salt neconditionat in codul de Ansembler.(fig 6.5)

C Ansembler

Signed int a,b,c; LDR R0,a

If (a>0) b=1 CMP R0,#0; Este a>0 ?

Else c=2; BLE ELSE

THEN: LDR R0,=1 ; b<-1

STR R0,b

B ENDIF ; Se sare peste ELSE

ELSE : LDR R0,=2 ; c=2

STR R0,c

END IF

Acest salt trebuie pus imediat dupa clauza (ramura) then (b=1) pentru a sari peste ramura else (c=2) astfel incat
doar o singura ramura va fi executata.

6.2.3.Instructiuni conditionate compuse


Acestea contin 2 sau mai multe conditii. Spre exemplu, sa consideram testul de incadrare a unei valori intr-un
domeniu minim si maxim.

If (!(x>=-100 && x<=100)) goto L1;


Y=x
L1:.......

Prin folosirea legilor lui D Morgan se obtine :


If (x<-100 || x>100)) goto L1,
Y=x
L1:.......
Astfel expresio or este mult mai simpla de transformat in ansembler.
C Ansembler

If (x<-100) goto L1; LDR R0,x

If (x>100) goto L1; CMN R0,#100 ; if(x<-100) goto L1

Y=x; BLT L1

L1:...... CMP RO,#100 ; if( x>100 ) goto L1

BGT L1

STR R0,y ; y=x

L1:...................

Sa consideram problema putin diferita in sensul ca se face testarea cu x, aflat in afara domeniului :

If(x<-100 || x>100) y=x;

Separam cele 2 conditii si obtinem:


If(x<-100) goto L1;
If(x>100) goto L1;
Goto L2;
L1:y=x
L2:.............

Putem elimina o instructiune goto si obtinem :


If(x<-100) goto L1;
If(x<=100) goto L2;
L1: y=x;
L2:.............
Prin transformarea acestui cod C in Ansembler obtinem :
LDR R0,x ; if(x<-100) goto L1
CMN R0,#100
BL L1

CMP R0,#100 ; If(x<=100 ) goto L2


BLE L2
L1: STR R0,y ; y=x
L2:................
6.2.4.Instructiunea IT (IF THEN)
Cand o instructiune if then controleaza o secventa scurta de instructiuni este recomandabil a se executa
instructiunea IT care este mai rapida.(fig 6.6)
Aceasta instructiune poate executa pana la 4 instructiuni conditionate. Acestea formeaza blocul IT.

C Ansembler

Signed int a,b,c; LDR R0,a

If (a>0) b=1; CMP R0,#0 ; este a>0 ?

Else c=2; ITTEE GT ; controleaza 4 instructiuni


LDRGT R0,=1 ; DA: b <- 1

STRGT R0,b

LDRLE R0,=2 ; NU:c<-2

STRLE R0,c

Prima instructiune din blocul IT este activa daca conditia din campul operand din instructiunea IT este adevarata.
Restul instructiunilor din bloc sunt controlate de una pana la 3 litere din mnemonica instructiunii IT. Daca litera este
T(then) sunt activate instructiunile corespunzatoare, daca litera este E(Else) sunt activate restul instructiunilor.
6.3.Implementarea buclelor
In general o bucla consta din 4 parti : initializare, testul pentru terminare sau continuare, reactualizarea variabilei
de control si campul public. Buclele se disting prin locul in care este plasat testul de finalizare/continuare: in fata sau la
sfarsitul corpului buclei. Pentru buclele while testul este plasat in fata corpului astfel incat aceasta se repeta de 0 sau mai
multe ori. Pentru buclele do while acest test este plasat la sfarsitul corpului si aceasta se repeta cel putin o data. Exista
bucle cu numar fix de iteratii. Toate buclele for, while sau do while prezinta un salt implicit de la sfarsitul, la inceputul
buclei. Acest salt devine explicit cand este translatat in ansamblare.(fig 6.7 si fig 6.8).

Fig 6.7 : cel mai mare divizor comun – cmmdc(a,b)


C Ansembler

Uint32_t a,b,gcd; LDR R0,a ; initializare

While(a!=b) LDR R1,b

{ if (a>b) a=-b; Top: CMP R0,R1 ; test pentru continuare (a!=b)

Else b-=a BEQ done

} ITE GT ; corpul buclei si actualizarea variabilei de conditie

Gcd=a SUBGT R0,R0,R1 ; a-=b

SUBLE R1,R1,R0 ; b-=a

B top ;salt implicit.

Done: STR

Fig 6.8 : calculul n factorial


C Ansembler

Uint32_t k,fact,n; LDR R0,=1 ; initializare

Fact=1; LDR R1,n ; fact=1 , k=n

For(k=n;k!=0;k--) Top: CMP R1,#0 ; test pentru continuare k!=0


{ BEQ done

Fact*=k; MUL R0,R0,R1 ; corpul buclei(fact*=n)

} SUB R1,R1,#1 ; actualizare (k--)

B top

Done : STR R0,fact

Buclele care se repeta de un numar predefinit de ori sunt atat de utilizate incat setul de instructiuni prezeinta 2
instructiuni (CBZ si CBNZ) care combina o comparatie si un salt intr-o singura instructiune. Spre exemplu instructinunile
BEQ si CMP din 6.8 pot fi inlocuite cu o singura instructiune : CBZ R1,done.
6.4.Implementarea functiilor
Pentru a scrie o functie in limbaj de ansamblare care sa poata fi aplicata si intr-un limbaj de nivel inalt este nevoie
de a sti mai mult decat modul cum se transfera controlul de la program la functia apelanta si inapoi, cum se transfera
parametrii si cum se primeste valoarea returnata . Compilatoarele prezinta reguli despre locul unde se gasesc parametrii
functiilor, valoarea returnata de aceastea, modul de salvarea a registrilor temporari si folosirea acestora.
6.4.1.Apelul si revenirea dintr-o functie
Exista 2 instructiuni pentru apelul si revenirea dintr-o functie prezentat in tabelul 6.3. (slide 3, ppt 7), instructiunea
Branch and link (BL) salveaza adresa de revenire (Adresa instructiunii urmatoare de dupa instructiunea BL) in registrul LR
si executa saltul la adresa de inceput a functiei. Registrul LR este folosit de instructiunea branch indirect BX pentru
revenire prin copierea continutului din LR in programul counter (PC).
Aproape orice instructiune care copie adresa de revenire in PC poate fi folosita (BX LR este echivalenta MOV
PC,LR) dar nu se recomanda varianta a 2 pentru a se mentine compatibilitatea cu setul de instructiuni ARM.
Cand o functie nu prezinta parametri si valoare returnabila, compilatorul genereaza o singura instructiune BL. Un
exemplu simplu de dezactivare a intreruperilor este prezentat in fig 6.9.

Prototip functie C Void DisableInterrupts(void);

Exemplu de apel in C DisableInterrupts();

Exemplu de apel in ASM BL DisableIntrerupts

Definierea functiei in ASM EXPORT DisableInterrupts

DisableInterupts:

CPSID ;dezactiveaza intreruperile

BXLR ; revenire din functie

Directiva EXPORT spune ansamblorului sa faca cunoscut numele functiei catre link-editor, aceasta putand fi
apelata si din alte fisiere din codul sursa. Registrul LR poate memora o singura adresa de revenire, astfel functiile care
apeleaza alte functii trebuie sa il memoreze la intrarea lor si sa refaca la iesire continutul original al acestuia. Codul din
figura 6.10 (slide 18 ppt 7) face acest lucru utilizand instructiunile PUSH si POP. Ultimele 2 instructiuni (POP {LR} si BX
LR) pot fi inlocuite cu instructiunea POP {PC} .
6.4.2 Folosirea registrelor
Pentru ca o functie scrisa in asamblare sa poata fi apelata si in cod C , aceasta trebuie sa fie compatibila cu
modul in care compilatorul se asteapta sa functioneze. Dincolo de un simplu mecanism de transfer a controlului si
revenire dintr-o subrutina , mai trebuie indeplinite anumite cerinte:
1. Unde gasim parametrii trimisi catre functia noastra?
2. Unde plasma valorile de returnare astfel incat functia apelanta sa le gaseasca?
3. Care registri trebuie salvati si care pot fi modificati?
Din fericire, aceste cerinte sunt specificate de ARM ARCHITECTURE PROCEDURE CALL STANDARD (AAPCS)
prezentate in tabelul 6.4 { slide 15, ppt7 } . Aceasta foloseste registrii r0 si r1 pentru a retine valoarea returnata. R0 e
folosit pentru a returna valori pe 8, 16 , 32 de biti . Valorile pe 64 de biti sunt returnate in r0 si r1 , in r1 memorandu-se
partea superioara. Registrii r0 pana la r3 pot fi modificati in interiorul functiei fara a fi necesara salvarea lor . Majoritatea
celorlalti registri pot fi folositi la fel , dar continutul lor original trebuie memorat inainte de a fi modificat si restaurat inainte
de revenirea din functie .
6.4.3 Transferul parametrilor
Registrii r0 pana la r3 sunt folositi pentru transferul parametrilor ( figura 6.11 ) si sunt de obicei suficienti pentru
rutinele mici, scrise in asamblare .
C ASM

Void display ( int 8_t, int 16_t[], int 64_t); ……

……. LDRSB R0,ch

Doit (ch,array, bignume); ADR R1, array

….. LDRD R2,R3,bignume

BL display

…..

Figura 6.11
Compilatorul incarca fiecare parametru de 8, 16 sau 32 de biti intr-un singur registru asignat de la stanga la dreapta ;
parametrii double word sunt plasati in registre consecutive ( r0 si r1, r1 si r2 sau r2 si r3 ) cu bitii MSB in al doilea registru
al perechii . Functia apelata poate modifica continutul registrilor r0 pana la r3 fara a fi nevoita sa refaca valoarea originala
a parametrilor continuti in ei .
6.4.4 Valorile de returnare
Se asteapta ca functiile sa returneze orice valoare intre 8, 16 sau 32 de biti in registrul r0 ( 0 filled sau signed
filled) in functie de tipul returnat ( cu sau fara semn) , figura 6.12 { slide 17 , ppt 7}
Functiile care returneaza valori pe 64 de biti trebuie sa faca aceasta folosind registrul r0 ( bitii 0-31) si r1 ( bitii 32-63) ,
figura 6.13 { slide 19 , ppt 7 }.
Daca o functie trebuie sa returneze mai multe rezultate , o practica comuna este aceea ca functia apelanta sa
trimita un pointer la o structura si sa lase functia apelata sa memoreze rezultate in structura
6.4.5 Memorarea registrelor
Functiile care modifica registrii trebuie in general sa-i memoreze si sa-i refaca folosind stiva . Din moment ce stiva
este retinuta in memorie, iar ciclii de memorare afecteaza performanta executiei (bottleneck) este important sa se salveze
cat mai putini registri posibili.
Situatia ideala ( functia f1 , figura 6.14 { slide 23 , ppt 7}) , care nu contine in interior alt apel de functie si care nu modifica
alti registri decat r0 pana la r3 , in acest caz nu este nevoie de memorarea registrelor.
Cand o functie modifica orice registru , altul decat r0 pana la r3, este necesara refacerea continutului original al acestuia.
Cel mai bun mod este de a pune acesti registri pe stiva, imediat dupa intrarea in functie si de a-i extrage de pe stiva
inainte de parasirea acesteia ( functia f2, figura 6.14 )
Cand o functie este apelata in interiorul altei functii, al doilea apel va suprascrie adresa de revenire din LR . De aceea
cand o functie ( functia f3 din figura 6.14 ) contine un apel ( BL ) catre o alta functie ( f4 ) , prima trebuie sa salveze
continutul LR pe stiva la intrare si sa-l refaca inainte de revenire .
Registrii r0 pana la r3 nu pot fi folositi de functiile care apeleaza alte functii pentru a pastra informatii , spre exemplu in
figura 6.14 , apelul functie f4 poate necesita parametri plasati in r0 pana la r3 si / sau pot modifica continutul lor inainte de
returnare. De aceea, o practica uzuala pentru f3 este de a muta parametrii sai in r4 pana la r11 si de a reface continutul
lor original , inainte de revenire .

S-ar putea să vă placă și