Estratto del documento

ISTRUZIONI E PSEUDOISTRUZIONI MIPS

ARITMETICA

add $s1, $s2, $s3 s1 ← s2 + s3 Addizione

addu $s1, $s2, $s3 s1 ← s2 + s3 Addizione naturale

addi $s1, $s2, cost s1 ← s2 + cost Addizione costante

addiu $s1, $s2, cost s1 ← s2 + cost Addizione costante naturale

sub $s1, $s2, $s3 s1 ← s2 - s3 Sottrazione

subu $s1, $s2, $s3 s1 ← s2 - s3 Sottrazione naturale

subi $s1, $s2, cost s1 ← s2 - cost Sottrazione costante Ψ

subiu $s1, $s2, cost s1 ← s2 - cost Sottrazione costante naturale Ψ

mult $s1, $s2 hi | lo ← s2 * s3 Moltiplicazione

mul $s1, $s2, $s3 s1 ← s2 * s3 Moltiplicazione Ψ

div $s1, $s2 hi ← s2 % s3 Divisione

lo ← s2 / s3

div $s1, $s2, $s3 s1 ← s2 / s3 Divisione Ψ

neg $s1, $s2 s1 ← -s2 Negazione aritmetica Ψ

LOGICA

and $s1, $s2, $s3 s1 ← s2 & s3 AND logico

andi $s1, $s2, cost s1 ← s2 & cost AND logico costante

or $s1, $s2, $s3 s1 ← s2 | s3 OR logico

ori $s1, $s2, cost s1 ← s2 | cost OR logico costante

nor $s1, $s2, $s3 s1 ← !(s2 | s3) NOR logico

not $s1, $s2 s1 ← !s2 NOT logico Ψ

sll $s1, $s2, cost s1 ← s2 << cost Shift a sinistra

srl $s1, $s2, cost s1 ← s2 >> cost Shift a destra

CONFRONTO

slt $s1, $s2, $s3 s1 = 1 if s2 < s3 Poni a 1 se <

sltu $s1, $s2, $s3 s1 = 1 if s2 < s3 Poni a 1 se < naturale

slti $s1, $s2, cost s1 = 1 if s2 < cost Poni a 1 se < costante

sltiu $s1, $s2, cost s1 = 1 if s2 < cost Poni a 1 se < costante naturale

SALTO CONDIZIONATO

beq $s1, $s2, addr if s1 == s2 Salta se uguale

bne $s1, $s2, addr if s1 != s2 Salta se diverso

blt $s1, $s2, addr if s1 < s2 Salta se minore Ψ

bgt $s1, $s2, addr if s1 > s2 Salta se maggiore Ψ

ble $s1, $s2, addr if s1 <= s2 Salta se minore o uguale Ψ

bge $s1, $s2, addr if s1 >= s2 Salta se maggiore o uguale Ψ

SALTO INCONDIZIONATO

j addr PC = addr Salto incondizionato

jr $ra PC = $ra Ritorno da funzione

jal addr PC = addr e salva $ra Chiamata a funzione

TRASFERIMENTO MEMORIA

lw $s1, offset($s2) s1 ← mem(s2 + offset) Carica word (4 byte)

sw $s1, offset($s2) mem(s2 + offset) ← s1 Memorizza word (4 byte)

lh,lhu $s1, offset($s2) s1 ← mem(s2 + offset) Carica half word (2 byte)

sh $s1, offset($s2) mem(s2 + offset) ← s1 Memorizza half word (2 byte)

lb,lbu $s1, offset($s2) s1 ← mem(s2 + offset) Carica byte

sb $s1, offset($s2) mem(s2 + offset) ← s1 Memorizza byte

TRASFERIMENTO TRA REGISTRI

mflo $s1 s1 ← lo Copia registro lo

mfhi $s1 s1 ← hi Copia registro hi

lui $s1, cost s1 (upper 16 bit) ← cost Carica costante su upper 16 bit

move $d, $s d ← s Copia registro Ψ

li $d, cost d ← cost (32 bit) Carica intero 32 bit Ψ

la $d, addr d ← addr (32 bit) Carica indirizzo 32 bit Ψ

REGISTRI REFERENZIABILI

0

0 Costante zero

at

1 Riservato per assemblatore e linker

v0-v1

2-3 Valori di ritorno delle funzioni

a0-a3

4-7 Argomenti delle funzioni

t0-t7

8-15 Registri per valori temporanei

s0-s7

16-23 Registri per variabili

t8-t9

24-25 Registri per valori temporanei (extra)

k0-k1

26-27 Riservati per il kernel del sistema operativo

gp

28 Global Pointer

sp

29 Stack Pointer

fp

30 Frame Pointer

ra

31 Return Address

In questo fighissimo corso parleremo dell'architettura dei calcolatori e dei sistemi

operativi. Impareremo a programmare in Assembly! Precisamente quello del processore

MIPS che, a detta del professore, è più semplice e intuitivo del classico e intricato 68000.

È quel mitico processore che è stato usato per le prime PlayStation e poi montato sulla

sonda New Horizons che è andata su Plutone.

L'Architettura è l'idea che ti fai della macchina considerando le istruzioni assembler che

compongono il linguaggio del processore utilizzato: l'accesso e la disponibilità di un certo

numero di registri, un metodo di accesso e utilizzo della memoria, il maneggiamento degli

operandi, la gestione dei tipi... L'idea che ti fai è l'architettura basata sul set di istruzioni

(ISA – Instruction Set Architecture). L'implementazione hardware poi è a se stante: due

processori con lo stesso linguaggio macchina sono uguali anche se hanno

implementazioni hardware differenti. È importante far notare che l'hardware è tanto “hard”

ma lo cambi in un attimo e nessuno se ne accorge. Il software lo chiamano “soft” ma ogni

modifica al software è un'apocalisse.

Il linguaggio ASSEMBLATORE è un linguaggio simbolico che ti permette di programmare

a basso livello in modo piacevole, con parole alfanumeriche che hanno un significato. Il

linguaggio MACCHINA invece è costituito interamente da tonnellate di 0 e di 1 e ti manda

ai pazzi. I due linguaggi sono quasi in corrispondenza 1:1, in pratica nel linguaggio

assemblatore possono esistere delle PSEUDOISTRUZIONI che in realtà equivalgono a

più istruzioni in linguaggio macchina (per esempio, una CPU non ha la moltiplicazione ma

la puoi implementare nel suo assembler con un'istruzione a parte). Come detto,

l'assemblatore è un linguaggio puramente simbolico, per cui possono esistere diverse

sintassi (che so in italiano, in inglese, in urdu o in pashto), ma genererebbe comunque lo

stesso codice macchina.

Durante la compilazione di un programma, naturalmente entra in gioco

l'ASSEMBLATORE che sostituisce i vari riferimenti e istruzioni simboliche con i

corrispettivi valori binari. Tuttavia di solito i programmi in assembly sono molto lunghi,

quindi vengono progettati da più programmatori, ognuno dei quali si dedica ad una parte

del programma. Il problema è che ovviamente ci sono variabili in comune, bisogna mettere

insieme i pezzi e fare in modo che ogni sottoprogramma si riferisca alla stessa variabile. È

qui che entra in gioco il LINKER! Prende i vari pezzi compilati, cerca di capire quali sono

le variabili in comune, ritocca il codice compilato e unisce tutto nell'eseguibile finale.

Esempio di progettazione di un compilatore C: non lo fai subito per una macchina

specifica, ma traduci in una via di mezzo, uno pseudo-assembly generico che "ti inventi tu"

e da quello puoi tradurre più facilmente in linguaggi macchina specifici. Puoi

tranquillamente fare un compilatore che traduce direttamente nel linguaggio macchina

specifico, dipende da cosa vuoi fare, ma la prima scelta è più furba :P

Ogni istruzione è composta da un codice operativo (opcode) che la identifica e dagli

operandi su cui l'istruzione agisce. Gli operandi possono essere impliciti nell'istruzione

stessa, oppure degli scalari numerici, dei registri, delle locazioni di memoria.

Esiste un'architettura generica di RIFERIMENTOH o di VON NEUMANN (anche se non è

esattamente sua) che è abbastanza chiara D: e ci dice che nella CPU oltre ai registri ci

stanno la ALU (che si occupa delle operazioni logico-matematiche), il registro PC

(Program Counter) che punta alla prossima istruzione da eseguire e il registro

contenente l'istruzione corrente. Per ogni istruzione il processore deve decodificare

l'istruzione, estrarre gli operandi, eseguire, memorizzare i risultati e incrementare il PC alla

prossima istruzione. La figata dei bit è che essi rappresentano sia istruzioni che dati,

quindi tecnicamente puoi eseguire un dato con una bella istruzione di salto :D

Un elemento importante nell'architettura di riferimento è l'UNITÀ DI CONTROLLO: un

circuito logico che gestisce i segnali da mandare alle varie periferiche ad ogni ciclo di

clock, il temporizzatore.

Informazioni storiche importanti: i processori vecchi di alcuni decenni fa avevano un'infinità

di istruzioni, tipo una trentina solo per fare somme e tante altre per operazioni inutili, ed

erano quindi macchine lente e complicate. Poi due tizi (guarda caso gli stessi del libro di

testo) hanno pensato che fosse possibile creare una CPU che fosse veloce con poche

istruzioni elementari (piuttosto che centinaia di istruzioni specifiche). Si pensava che non

ce l'avrebbero fatta, e invece hanno sbaragliato la concorrenza, perché con la loro

tecnologia è stato possibile semplificare la progettazione di hardware e compilatori,

massimizzare le prestazioni e diminuire i costi. Intel e IBM hanno dovuto convertirsi a

questi nuovi processori (RISC – Reduced Instruction Set Computer) e, per salvare

capre e cavoli, affiancarli ad un preprocessore che in tempo reale traducesse il vecchio

linguaggio macchina complicato (CISC) in quello semplificato (solo ed esclusivamente per

motivi di compatibilità). Ma erano proprio istruzioni lunghissime, magari da 128 bit e faceva

impazzire tutto, oppure cinquanta alternative per un salto incondizionato D: invece qua

ogni istruzione ha una dimensione fissa quindi non si ha nessun problema nell'estrazione

delle istruzioni. Queste architetture però hanno un vincolo: non è possibile maneggiare i

dati direttamente sulla memoria. Ogni operando dalla memoria deve essere caricato nei

registri, poi la ALU fa il suo dovere, e infine i risultati dai registri vengono messi in

memoria. Sembra uno spreco di risorse, ma nella gestione di grandi dati non è così.

Come detto nel processore MIPS ogni istruzione occupa esattamente 32 bit. Di sicuro dal

libro degli hacker ti ricorderai cosa significano BIG-ENDIAN e LITTLE-ENDIAN. Eh

confessa! Beh, il nostro MIPS è big-endian, anche se è possibile usarlo in little-endian.

Il MIPS dispone di vari registri che si suddividono in REFERENZIABILI e NON

REFERENZIABILI. Questi ultimi in sostanza sono quelli che non si possono toccare. I

primi invece sono 32 e proprio da 32 bit ciascuno. I vari registri sono consigliati per

essere utilizzati ad un certo scopo, ma teoricamente puoi usare che so, lo stack pointer

per memorizzare i valori di ritorno di una funzione. Tuttavia un po' di “igiene mentale” ti

dice di non farlo, anche perché il MIPS è un processore poco protetto, ti lascia fare

stronzate. Se un registro è riservato, NON TOCCARLO. Altrimenti tu fai casino, il

calcolatore fa casino, poi se il programma lo fanno più persone è ancora più casino.

QUALI SONO LE ISTRUZIONI?

Aritmetiche-logiche (compito della ALU)

– Trasferimento da o verso memoria/registri

– Modifica del flusso di esecuzione (salti condizionati e non, sottoprogrammi)

– Trasferimento dati input/output tra periferiche

– Istruzioni speciali di controllo (complicato da spiegare adesso)

TUTTE le istruzioni macchina MIPS per semplicità, velocità, prestazioni e minori consumi

hanno le stesse dimensioni: 32 bit, cioè una parola. Di questi i primi 5 sono riservati

all'OPCODE, che dice cosa fa l'istruzione e a cosa servono i bit rimanenti.

Le istruzioni si possono dividere in tre tipi:

Tipo R (register): istruzioni aritmetico-logiche, che coinvolgono la ALU e i registri

– Tipo I (immediate): accesso alla memoria e salti condizionati

– Tipo J (jump): salti incondizionati

Esistono cinque modalità di INDIRIZZAMENTO nel MIPS:

Immediato

– L'operando è già inserito nell'istruzione.

A registro

– L'operando è un valore contenuto in un registro.

Con base e spiazzamento

– Un indirizzo viene calcolato usando un registro base a cui si somma un offset.

Relativo al Program Counter

– Come base e spiazzamento, ma relativo al Program Counter

Pseudo-diretto

– Indirizzi assoluti inseriti nel Program Counter

Ora ci facciamo una simpatica carrellata di istruzioni Assembly!

ISTRUZIONI ARITMETICO-LOGICHE

Le istruzioni aritmetico-logiche in MIPS hanno tutte tre operandi, dei quali il primo è il

registro di destinazione (che memorizza il risultato) mentre gli altri due sono i registri

sorgente. Sono quindi istruzioni di tipo R, l'ordine degli operandi è fisso e ricorda

facilmente i linguaggi di alto livello.

add rd, rs, rt # rd ← rs + rt

sub rd, rs, rt # rd ← rs – rt

addu rd, rs, rt # addizione unsigned (tipo I)

subu rd, rs, rt # sottrazione unsigned (tipo I)

and rd, rs, rt # rd ← rs & rt

or rd, rs, rt # rd ← rs | rt

sll rd, rs, 10 # rd ← rs << 10

srl rd, rs, 10 # rd ← rs >> 10

Esistono varianti con operando immediato:

addi rd, rs, cost # rd ← rs + cost

addiu rd, rs, cost # addizione unsigned

Un paio di esempi di traduzione da C a MIPS:

1) R = A + B;

→ add $s0, $s1, $s2

2) A = B + C + D; # A = $s0, B = $s1, C = $s2...

E = F – A;

→ add $t0, $s2, $s3

add $s0, $t0, $s1

sub $s4, $s5, $s0

ISTRUZIONI DI TRASFERIMENTO LOAD/STORE

Sappiamo che nel MIPS non possiamo maneggiare direttamente la memoria, ma

dobbiamo caricare e scaricare i dati con istruzioni apposite. Queste istruzioni hanno due

operandi: il primo è la sorgente/destinazione mentre il secondo è l'indirizzo di memoria

interessato. In realtà hanno un terzo argomento, perché l'indirizzo che cerchiamo viene

identificato con la struttura offset(registro_base) dove l'offset è uno spiazzamento di 16

bit e il registro base è il registro da cui si calcola lo spiazzamento. Tale meccanismo ci

serve perché gli indirizzi di memoria occupano 32 bit, ma noi non possiamo inserire tutti i

32 bit nell'istruzione perché non ci stanno! Allora con questa furbata usiamo l'identificativo

di un registro e un offset, così possiamo navigare nella memoria come vogliamo :D

Queste istruzioni quindi hanno tre argomenti: il registro coinvolto nel trasferimento,

l'offset, il registro base. In generale l'indirizzo della parola di memoria è calcolato come

identificatore + offset + registro base (dove l'identificatore è l'etichetta data ad una certa

variabile la cui posizione verrà calcolata dal linker). Un po' di esempi che è meglio:

lw $t0, ($a0) # senza offset: t0 ← M[a0 + 0]

lw $t0, 20($a0) # con offset: t0 ← M[a0 + 20]

lw $t0, var1 # identificatore: t0 ← M[gp + …]

lw $s1, 100($s2) # s1 ← M[s2 + 100]

sw $s1, 100($s2) # M[s2 + 100] ← s1

Esempio di traduzione C → MIPS:

A[12] = h + A[8]; # h in $s2, &A[0] in $s3

→ lw $t0, 32($s3) # carica A[8] (indirizza i byte!)

add $t0, $t0, $s2

sw $t0, 48($s3)

Insomma un bel macello... Può esserti utile ricordare che, non essendoci la moltiplicazione

e la divisione, puoi fare cose più belle, tipo sommare più volte o, ancora più

intelligentemente, usare uno shift logico per moltiplicare o dividere per potenze di 2 :D

ISTRUZIONI DI SALTO

Le istruzioni di salto condizionato e incondizionato permettono di saltare in giro per il

programma. Come vedi, il tanto odiato goto del C è pane quotidiano in Assembly :D

L'indirizzo della cella di destinazione del salto viene solitamente indicato con un'etichetta.

I salti condizionati sono istruzioni di tipo I e di fatto consistono nell'aggiunta di un offset di

16 bit al Program Counter per modificare il flusso di esecuzione.

I salti incondizionati invece sono istruzioni di tipo J e fanno indirizzamento pseudo-

diretto.

Le istruzioni di salto condizionato (conditional branch) vengono eseguite se una

determinata condizione viene soddisfatta.

beq $s1, $s2, LABEL_1 # if s1 == s2

bne $s1, $s2, LABEL_2 # if s1 != s2

Per le condizioni di maggioranza/minoranza si usano altri metodi simpatici :)

Le istruzioni di salto incondizionato (unconditional branch) vengono sempre eseguite.

j LABEL_1 # salta e basta

jr $ra # salta a indirizzo in $ra

jal LABEL_1 # salta e salva indirizzo in $ra

Queste ultime due istruzioni equivalgono praticamente al ret e call dell'x86.

Esempio di traduzione C → MIPS:

if (i == j) # f in $s0, g in $s1...

f = g + h;

else f = g – h;

→ bne $s3, $s4, ELSE

add $s0, $s1, $s2

j END_IF

ELSE: sub $s0, $s1, $s2

END_IF:

Nota due cose importanti da questo esempio: normalmente non si controlla la condizione

di if ma quella di else per fare un salto, e poi al termine dell'if devi mettere un salto

incondizionato altrimenti quello esegue pure l'else!!!

Per le condizioni è utile il registro $0 che contiene la costante zero e non può essere

modificato. Inoltre il REGISTRO DI STATO contiene delle flag risultati dalle operazioni di

confronto. Alcune di queste sono:

N Risultato negativo

Z Risultato zero

V Segnale di overflow

C Risultato con riporto

Ma come facciamo il maggiore e minore? Con una bella istruzione: slt (set less than) setta

il primo registro a 1 se il secondo è minore del terzo, altrimenti lo mette a zero. Unito a

beq e bne questa istruzione implementa TUTTI gli operatori di confronto!

if (i < j) # i in $s0, j in $s1, k in $s2

k = i + j;

else k = i - j;

→ slt $t0, $s0, $s1 # t0 contiene 1

beq $t0, $0, ELSE

add $s2, $s0, $s1

j END_IF

ELSE: sub $s2, $s0, $s1

END_IF:

PSEUDOISTRUZIONI

Le pseudoistruzioni sono istruzioni fornite dal linguaggio Assembly ma che in realtà non

esistono nel linguaggio macchina. È l'assemblatore che le traduce nelle corrispondenti

istruzioni. Un paio di esempi:

Pseudoistruzione Equivalente reale

move $t0, $t1 add $t0, $0, $t1

mul $s0, $t0, $t1 mult $t1, $t2

mfhi $s0

La move sposta il contenuto del secondo registro nel primo. Questa istruzione in realtà

non esiste perché dovrebbe richiedere tre operandi e non due. L'istruzione effettivamente

tradotta in linguaggio macchina infatti fa una addizione con zero (oppure un or logico).

La mul effettua la moltiplicazione e salva il risultato in un registro: in realtà si usa la mult

che effettua la moltiplicazione e salva il risultato nei registri hi e lo. Con mfhi dici di

salvare il contenuto del registro hi in un altro registro.

Anteprima
Vedrai una selezione di 14 pagine su 65
Appunti completi corso Architettura dei Calcolatori Pag. 1 Appunti completi corso Architettura dei Calcolatori Pag. 2
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 6
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 11
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 16
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 21
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 26
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 31
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 36
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 41
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 46
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 51
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 56
Anteprima di 14 pagg. su 65.
Scarica il documento per vederlo tutto.
Appunti completi corso Architettura dei Calcolatori Pag. 61
1 su 65
D/illustrazione/soddisfatti o rimborsati
Acquista con carta o PayPal
Scarica i documenti tutte le volte che vuoi
Dettagli
SSD
Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher fiorixf2 di informazioni apprese con la frequenza delle lezioni di Architettura dei calcolatori e sistemi operativi e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Politecnico di Milano o del prof Negrini Roberto.
Appunti correlati Invia appunti e guadagna

Domande e risposte

Hai bisogno di aiuto?
Chiedi alla community