Sei sulla pagina 1di 3

Il Livello Assembler: Approfondimento e ripasso

Abbiamo visto la suddivisione a livelli di un elaboratore. Abbiamo visto come i livelli interagiscono
tra di loro. Esaminiamo un po’ meglio il livello assembler. Abbiamo detto che il linguaggio che
consente di gestire le risorse a questo livello è il linguaggio macchina o assembly. Il linguaggio
macchina è interpretabile direttamente dal firmware del calcolatore. Il Firmware è la parte
progettata per ottenere un corretto funzionamento degli scopi che ci eravamo proposti (reti
logiche opportunamente connesse!).
Di conseguenza il linguaggio macchina è il linguaggio che il programmatore o progettista di un
compilatore devono impiegare per controllare le risorse di un calcolatore.
La trasformazione avviene tipicamente per passi:
1. nel primo passo i programmi sorgente sono trasformati in programmi assembler, un linguaggio
intermedio dove le istruzioni di macchina sono associate a nomi simbolici più facili da capire e
manipolare per i programmatori;
2. nel secondo passo il programma assembler è tradotto, da un programma detto assemblatore,
nel linguaggio macchina vero e proprio (ovvero in sequenze binarie direttamente interpretabili
dal calcolatore);
3. nel terzo, ed ultimo passo, il programma eseguibile viene caricato in memoria da un
programma detto caricatore (loader) e quindi eseguito.
Tutti i passi per tradurre un programma ad alto livello in linguaggio macchina sono mostrati nella
seguente figura:

Compilatore Assemblatore Caricatore


del del (Loader)
linguaggio linguaggio

Programma Programma Programma


Linguaggio Memoria
C#, Assembler
C++, java Macchina

La scelta delle istruzioni eseguibili direttamente dal calcolatore (set istruzioni), ha conseguenze
immediate sulla progettazione dell’architettura, sul suo costo e sulle prestazioni che questa può
garantire. Il set di istruzioni di un calcolatore può essere classificato usando diversi criteri, tuttavia,
la classificazione più diffusa è quella basata sulle modalità di memorizzazione ed accesso agli
operandi da parte della CPU. In base a questo criterio possiamo distinguere almeno tre categorie:
1. Architetture a Pila (Stack)
2. Architetture ad Accumulatore
3. Architetture a Registri Generali

Architettura a pila
Le architetture di questo tipo assumono implicitamente che gli operandi si trovino in memoria e in
particolare nella testa di un’area speciale detta pila di sistema. Le istruzioni di calcolo devono
perciò essere precedute e seguite da spostamenti espliciti degli operandi mediante operazioni di
PUSH (inserimento sulla pila) e POP (estrazione di un risultato dalla pila).

Architettura ad accumulatore
Nelle architetture ad accumulatore si assume implicitamente che uno degli operandi sia contenuto
in un registro speciale detto accumulatore. Gli altri operandi vengono prelevati dalla memoria.

Architetture a registri generali


Nelle architetture a registri generali gli operandi sono tutti espliciti e possono trovarsi in memoria
o nei registri a seconda della tipologia di istruzioni disponibili.
Riassumiamo nella seguente tabella un confronto tra le tre tipologia per la realizzazione di una
semplice operazione C = A + B:

Pila Accumulatore Registri Generali*


Push A Load A Load R1, A
Push B Add B Load R2, B
Add Store C ADD R3,R1,R2
Pop C Store C, R3
Le architetture a Registri Generali possono essere suddivise in base alla modalità con la quale
accedono alla memoria. Concettualmente esistono infatti tre tipi di architetture:
 Memoria-Registro: in queste architetture esistono operazioni che hanno parte degli
operandi in memoria e parte nei registri;
 Registro-Registro: tutti gli operandi stanno nei registri e le uniche operazioni per accedere
alla memoria sono operazioni di Load/Store;
 Memoria-Memoria: in queste architetture esistono operazioni che leggono gli operandi in
memoria e vanno a scrivere i risultati direttamente in memoria.
Nella tabella di confronto (*), l’architettura a registri generali considerata è di tipo
Memoria-Memoria.
In generale l’utilizzo di un set di istruzioni ridotto (poche e semplici istruzioni) aiuta la
progettazione di un calcolatore avente una struttura regolare quindi più facile da realizzare e
meno costoso. Questo approccio è nato agli inizi degli anni ’80 ed è detto RISC: Reduced Instructin
Set Computer. Di contro l’utilizzo di un set di istruzioni più complesso riduce il numero di istruzioni
di macchina necessarie a realizzare un programma ad alto livello riducendo così la dimensione dei
programmi e la conseguente occupazione di memoria. Questo approccio prende il nome di CISC:
Complex Instruction Set Computer.
Modi Di Indirizzamento
Uno dei problemi più importanti nella definizione di un set istruzioni è la scelta delle modalità con
le quali sono individuati gli argomenti all’interno delle istruzioni. Utilizzare modalità più complesse
può ridurre il numero delle istruzioni necessarie a scrivere un programma ma nel contempo fa
aumentare, in media, il numero dei cicli necessari ad eseguire un’istruzione nonché la complessità
dell’architettura stessa.
I modi di indirizzamento più utilizzati nelle architetture a registri generali sono evidenziati nella
tabella seguente, utilizzando una semplice operazione di tipo aritmetico logico:

Registro Add R1, R2 Reg[R1]←Reg[R1]+Reg[R2]

Immediata Add R1, #K Reg[R1]←Reg[R1]+K

Displacement Add R1, K(R2) Reg[R1]←Reg[R1]+Mem[K+ Reg[R2]]

Indiretta Add R1, (R2) Reg[R1]←Reg[R1]+Mem[Reg[R2]]

Indice Add R1, R2+R3 Reg[R1]←Reg[R1]+Mem[Reg[R2] + Reg[R3]]

Assoluta Add R1, (K) Reg[R1]←Reg[R1]+Mem[K]

Memoria Indiretta Add R1, @(R2) Reg[R1]←Reg[R1]+Mem[[Men[Reg[R2]]]

Si osservi che nel caso delle architetture Registro-Registro i modi di indirizzamento che specificano
indirizzi di memoria sono rilevanti solo per istruzioni di Load e Store.
Per le istruzioni che trasferiscono il controllo di programma (come le istruzioni di salto o di
chiamata di procedura) esiste un’ulteriore modalità di indirizzamento che è la così detta modalità
relativa al registro di Istruzioni (IC = Instruction Counter a volte PC= Program Counter).
Questa modalità consiste nel fornire un displacement da sommare al valore attuale del registro
istruzioni invece di specificare l’indirizzo assoluto.
Questa modalità garantisce due interessanti proprietà:
 Riduce il numero di bits necessari per specificare l’indirizzo di destinazione;
 Consente di scrivere programmi che possono essere eseguiti in diverse posizioni della
memoria senza dover essere modificati.