Sommario
< Home
Stampa

Codice assembly

Codice macchina e codice assembly

In un calcolatore, le istruzioni macchina sono in codice binario. Esse vengono decodificate direttamente dalla cpu (hardware) che poi esegue le istruzioni operando sui dati (registri e memoria). 

Possiamo in teoria usare direttamente il linguaggio macchina (che nella quasi totalità delle implementazioni è come sappiamo in codice binario), ma per rendere questo codice comprensibile agli esseri umani, senza perdere in validità, gli sviluppatori preferiscono usare il linguaggio assembly, che da una corrispondenza delle istruzioni codice macchina con codici alfanumerici che rappresentano istruzioni, registri e valori). 

In questa lezione introduciamo una versione puramente didattica del linguaggio assembly, con un set limitato di istruzioni.

Nel presentare le istruzioni si ricorda che:
– la CPU presenta i registri di servizio PC, IR e SP (Program Counter, Instruction Register e Stack Pointer)

– la CPU presenta i registri dati R0… R9

– la memoria centrale è esterna alla cpu e ci si accede per caricare/scaricare celle da/verso i registri.

In linguaggio assembly ogni istruzione ha la forma:

#INDIRIZZO: CODICEISTRUZIONE operando1, operando2, operando3 

dove gli operandi possono essere da 0 a 3 (a seconda dell’istruzione).

Gli operandi possono essere:

– registri: nominati con prefisso $: ad esempio $R5, prende il valore contenuto nel registro

– valori assoluti: si indica il numero senza prefissi (es. “5”)

– indirizzi di memoria: si indica il numero con prefisso # (es. “#250” dove 250 è l’indirizzo di una cella di memoria)

Vediamo quindi le istruzioni:

OperazioniElenco
operazioni aritmetiche (somma, prodotto, ecc.) e di confronto tra operandi, eseguite dalla ALU: 
ADD, SUB, MUL, DIV il primo operatore è dove si memorizza il risultato (sempre un registro), gli altri due sono gli operandi (possono essere o registri o valori assoluti)
ad esempio: 
ADD $r0, 1, $r1 -> salva in $r0 il valore di $r1 +
MULT $r5, 2, $r3 -> moltiplica $r3 * 2 e salva in $r5 CMP (compare)
compara due valori.ad esempio: 
CMP $r0, $r2, $r5 -> metto in $r5 il valore 0 se sono uguali, altrimenti -1 se minore, +1 se maggiore 
operazioni di controllo, ad esempio salti ad una istruzione specifica (a seconda di una condizione o liberi), eseguiti dalla CUJMP (salto)
es: JMP #50 -> salta all’indirizzo 50 
BNE (salto condizionato su diseguaglianza)
es. BNE $r0, 5, #81 -> salta all’indirizzo 81 se r0 è diverso da 5 
BEQ (salto condizionato su uguaglianza)Es:
BEQ $r0, 3, $r1 – > salto all’indirizzo contenuto nel registro r1 se r0 è uguale a 3
operazioni di caricamento di registri dalla memoria e viveversa, eseguiti dalla CU STO sorgente destinazione (save), 
LOD destinazione sorgente (load)carica e salva un dato dalla memoria
Es: 
STO $r0 #451 -> memorizzo il valore di $r0 nella cella #451
LOD $r6 $r2 -> carico il valore all’indirizzo indicato da r2 in r6
SubroutineCAL XXX: questa istruzione salva all’indirizzo $SP l’indirizzo dell’istruzione successiva, decrementa di 1 $SP ed esegue una jump all’indirizzo XXX.
RET Questa istruzione copia in $PC il valore di $SP L’istruzione gestisce da sola l’incremento dello stack pointer. E’ necessario l’utilizzo del registro $SP per eseguire push e pop, aggiornando la posizione dello stack dopo ogni push e pop. Si ricorda che lo stack cresce dall’alto verso il basso (quindi $SP diminuisce di 1 con push, aumenta di 1 con pop). 
Es:
CAL #212 -> memorizza indirizzo di ritorno in stack, aggiorna lo stack pointer, ed esegue un salto all’indirizzo 212.
RET -> carica l’indirizzo di ritorno dallo stack, aggiorna lo stack (pop) ed esegue il salto all’istruzione indicata

Un esempio pratico

Esempio: vogliamo memorizzare la somma degli interi da 1 a n (cioè 1+2+3+…+n). 

Qui il codice C++:

int sum(int n) {  
  int result = 0;  
  for (int i=0; i<n; i++) {    
   result += i;  
  }  
  return result;
}

Useremo i seguenti registri per memorizzare il valore delle variabili:

$r0 -> n

$r1 -> result

$r2 -> i

$r3 -> registro di servizio che useremo per gestire il ciclo

$r8 -> contiene l’indirizzo di memoria fisica dove si trova n (inserito dall’utente)

$r9 -> contiene l’indirizzo di memoria fisica dove memorizzare il risultato result alla fine

Vediamo il codice (che ipotizziamo partire dall’indirizzo #10)

#10: LOD $r0, $r8 carichiamo in $r0 il valore della cella di memoria il cui indirizzo è in $r8
#11: LOD $r1, 0 carichiamo in $r1 il valore 0 (valore iniziale di result)
#12: LOD $r2, 0 carichiamo in $r2 il valore 0 (valore iniziale di i)
#13: ADD $r2, $r2, 1 incrementiamo $r2 di 1
#14: ADD $r1, $r1, $r2 sommiamo a $r1 il valore di $r2
#15: SUB $r3, $r0, $r2 memorizziamo in $r1 il risultato della sottrazione $r0 - $r2 (n-i)
#16: BNE $r3, 0, #13 se il risultato non è 0 (cioè se n-i != 0) allora saltiamo all'istruzione #13 (3 è l'etichetta) e il ciclo ricomincia
#17: STO $r1, $r9 memorizza nell'indirizzo di memoria indicato da $r9 il valore di $r1, cioè il risultato

TPSI – Elenco istruzioni assembly

Elenco istruzioni con tutti gli operatori

IstruzioneEsempiSignificato
ARITMETICO-LOGICHE
ADD registro, operando1, operando2ADD $r0, 1, 2
ADD $r0, $r1, $r2
ADD $r0, $r1, 1
Somma.Operando1 e operando2 possono essere valori dei registri o valori assoluti
SUB registro, operando1, operando2SUB $r0, 1, 2
SUB $r0, $r1, $r2
SUB $r0, $r1, 1
Sottrazione.Operando1 e operando2 possono essere valori dei registri o valori assoluti
MUL registro, operando1, operando2MUL $r0, 1, 2
MUL $r0, $r1, $r2
MUL $r0, $r1, 1
Moltiplicazione.Operando1 e operando2 possono essere valori dei registri o valori assoluti
DIV registro, operando1, operando2DIV $r0, 1, 2
DIV $r0, $r1, $r2
DIV $r0, $r1, 1
Divisione.Operando1 e operando2 possono essere valori dei registri o valori assoluti
CMP registro, operando1, operando2CMP $r0, 1, 2
CMP $r0, $r1, $r2
CMP $r0, $r1, 1
Comparazione. Operando1 e operando2 possono essere valori dei registri o valori assoluti.Il risultato è:-1 se operando1 < operando20 se uguali+1 se operando2 > operando1
SALTO
JMP operandoJMP $r0JMP #232Salto.L’operando può essere un registro (il cui valore sarà considerato un indirizzo) o un valore assoluto.
BNE operando1, operando2, operando3BNE $r0, $r1, #232BNE $r0, $r1, $r3BNE $r0, 5, #232BNE 3, $r1, $r1Salto condizionato: eseguito se i valori degli operandi non sono uguali. Operando1 e operando2 possono essere valori dei registri o valori assoluti.Operando3 può essere un indirizzo assoluto oppure un registro (il cui valore sarà considerato un indirizzo)  
BEQ operando1, operando2, operando3BEQ $r0, $r1, #232
BEQ $r0, $r1, $r3
BEQ $r0, 5, #232
BNE 3, $r1, $r1
Salto condizionato: eseguito se i valori degli operandi sono uguali. Operando1 e operando2 possono essere valori dei registri o valori assoluti.Operando3 può essere un indirizzo assoluto oppure un registro (il cui valore sarà considerato un indirizzo)  
CAL indirizzoCAL $r0CAL #300Esegue una push sullo stack con il valore dell’indirizzo dell’istruzione successiva, ed esegue un salto alla destinazione indicata come operando.
RETRETEsegue una pop dallo stack di un indirizzo, e vi esegue un salto. 
MEMORIA
STO sorgente, destinazioneSTO $r0, $r1
STO 5, $r1
STO 5, #128
STO $r1, #421
Salva un valore in memoria. 
La sorgente può essere un registro o un valore assoluto.La destinazione può essere un registro (il cui valore sarà considerato un indirizzo), o un indirizzo assoluto.
LOD destinazione, sorgenteLOD $r0, $r1
LOD $r1, 5
LOD $r2, #128
Carica un valore dalla memoria. 
La sorgente può essere un registro (il cui valore sarà considerato un indirizzo), un valore assoluto o un indirizzo assoluto. La destinazione deve essere un registro. 

Note:

– $ indica il prefisso di un registro, # indica un prefisso di un indirizzo, senza prefissi è un valore assoluto;

– i registri della CPU sono PC (program counter), IR (istruzione corrente) e SP (indirizzo prima cella libera dello stack), ed infine R0…R9 (registri dati);

– si ricorda che in condizioni normali al termine di una istruzione, il PC viene incrementato di 1;

– l’esecuzione di una istruzione di salto consiste nel sovrascrivere il PC con l’indirizzo verso cui bisogna saltare;

– occorre fare attenzione al contenuto dei registri:

– nelle istruzioni aritmetico-logiche, il contenuto del registro è considerato un valore;

– nelle istruzioni di salto, quando viene usato un registro per indicare l’istruzione verso cui saltare, il valore sarà considerato un indirizzo (quindi “5” sarà considerato “#5”).

– nelle istruzioni di memoria, la sorgente è sempre un valore, la destinazione è sempre un indirizzo.

– la PUSH verso lo stack è sempre data dalla coppia di istruzioni:

STO $sp, value
SUB $sp, $sp, 1

in quanto è necessario aggiornare SP alla prima posizione libera.

– la PULL dallo stack è sempre data dalla coppia di istruzioni:

ADD $sp, $sp, 1
LOD $rX, $sp

dove RX è un generico registro. E’ necessario in questo caso aggiornare SP all’ultima posizione occupata.

In conclusione

– per un semplice ciclo sono necessarie più operazioni a basso livello rispetto a quelle ad alto livello. 

– il codice macchina (di cui vediamo) è composto da istruzioni semplici e poco costose da realizzare 

– le istruzioni hanno lo stesso tempo di esecuzione

Un set di istruzioni reale è molto più ampio, e si diversifica per architettura, secondo tecnologie che si sono evolute soprattutto con l’avvento dei microprocessori e le architetture moderne, come RISC e CISC (che differivano proprio per la complessità delle istruzioni) con le loro più recenti evoluzioni (X86  -tecnologia del mercato desktop – e ARM – tecnologia del mercato mobile).