Estratto del documento

LOGICHE E FUNZIONAMENTI DEL MIPS

Maggiore, Minore,…: tra <= >= < >, l’unico che esiste realmente è il simbolo <. Gli

altri possono essere comunque usati ma sono in realtà tutti simulati grazie a <

(pseudo istruzioni).

Somma in virgola mobile: esempio: programma che somma 0,2 e quando arriva a 10

termina -> non arriverà mai a 10 perché 0,2 in virgola mobile non è potenza di 2,

quindi è periodico. Se sommi 0,25 allora arriverà a 10.

Gestione Overflow: Generalmente useremo il complemento a2 per le operazioni

(tranne rari casi). Normalmente si usa il flag dell’overflow per capire se l’operazione

è riuscita, ma nel nostro processore non è presente il flag dell’overflow. Quindi

useremo un’altra tecnica: procediamo senza verificare se c’è l’overflow, quindi se

non avviene siamo apposto e abbiamo anche evitato di controllare il flag, se invece

l’overflow accade, allora la alu comunicherà con il controllore il quale fermerà la

prosecuzione del programma per dare modo di gestire la situazione per poi

riprendere. E’ vantaggioso perché si evita ogni volta di controllare il flag, ma

svantaggioso perché in caso di overflow bisogna ricordarsi in che punto

dell’esecuzione eravamo rimasti (per poi poter riprendere). Quindi questo metodo è

vantaggioso solo nel caso l’overflow si verifichi raramente. Nel nostro processore c’è

un bus dei dati di 32 bit quindi è raro che i bit non bastino, allora ci va bene. Inoltre,

quando il PC serve per andare alle routine che gestiscono l’overflow, esso viene

salvato nel EPC (Exception Program Counter) in modo da non andare perso, verrà

poi recuperato quando saranno terminate le routine per la gestione dell’overflow (per

copiare l’EPC in un registro k si usa mfc0 [move from core processor 0]). Sub

$8,$zero,$9 può dare overflow se $9 contiene il minimo rappresentabile in a2.

Gestione sottoprogrammi Program Counter e Stack: Visto che raramente un

sottoprogramma viene chiamato in un altro sottoprogramma (può succedere ma non

spesso), non viene utilizzato lo stack pointer e lo stack inizialmente. L’indirizzo da

ricordare per riprendere da dove eravamo rimasti non va nello stack ma in un

program counter di riserva (Return Address)(jump and link è saltare e salvare nel RA

lo stato). In questo modo, se c’è un'unica nidificazione (come spesso accade) è tutto

più veloce perché viene utilizzato un registro interno alla cpu e non lo stack.

Se dovesse avvenire un’altra nidificazione, prima di farla, verrà salvato nello stack il

valore del return address. Quando la seconda nidificazione è terminata, l’indirizzo

che era stato salvato nello stack, viene rimesso nel return address, per permettere,

al termine della prima nidificazione, di tornare nel main.

L’utilizzo del return address per tornare al punto in cui ero rimasto è automatico,

mentre il salvataggio del return address nello stack (e viceversa il riportare il valore

dello stack nel return address) deve essere fatto da noi scrivendo le istruzioni.

“PUSH”= addi $sp,$sp,-4 Sw $ra,0($sp) [-4 perché lo stack è da alto a basso]

“POP”= lw $ra,($sp) Addi $sp,$sp,4

Dimensione celle di memoria: Nella memoria (RAM) ogni cella è grande 4 byte,

quindi gli indirizzi vanno di 4 in 4 (per le word)

Formato delle istruzioni: Ogni istruzione è formata da 32 bit (4 byte). Ma come

vengono utilizzati e suddivisi questi 32 bit? Esistono 3 tipi di formato:

● Formato R: usato da add,sub,shift,… 6 bit Codice Operativo, 5 bit per ciascun

“operando” per identificare uno dei 32 registri (sono 3 operandi quindi 15 bit),

5 bit di shift amount

(usato in caso di

istruzione shift), 6 bit di

func (specifica meglio

l’istruzione, ad esempio

ci fa distinguere add e

addi…)

● Formato I: usato da lw,sw,beq,bne,… 6 bit Codice Operativo, 5 bit per ciascun

“operando” per identificare uno dei 32 registri (sono 2 operandi quindi 10 bit),

16 bit per il numero da aggiungere al PC per il salto di beq/bne (si utilizza un

indirizzamento relativo al PC, il numero sarà in complemento A2 in modo da

poter fare anche spostamenti negativi. Questo numero verrà moltiplicato per 4

in automatico prima di

essere sommato al PC) o

per il numero per lo

spostamento dell’indirizzo di

lw/sw (es 32($s2)).

[(pc+4)+100] pc +4 in fetch

PS: Visto che lo spostamento del bne/beq è limitato, si può anche sostituire

un beq con un bne e un jump, e il jump farà il grosso salto che bne non

poteva fare. ES: beq $t0,$t1,Poi Poi: → bne $t0,$t1,L1 j Poi L1:... Poi:

● Formato J: usato da j (jump incondizionato). 6 bit Codice Operativo, 26 bit per

l’indirizzo. 26 bit non basterebbero per un indirizzo a cui saltare

(mancherebbero 4 bit per avere un indirizzo completo [non 6 perché gli ultimi

due bit sono 0 visto che è multiplo di 4]), allora si fa un indirizzamento semi

diretto (non sicuro che si chiami così) cioè vengono tenuti i primi 4 bit del PC

poi vanno i 26 bit presi dall’istruzione e infine due zeri. Usando questa tecnica

però, non si può andare da

qualunque luogo a qualunque

altro, ma posso muovermi

ovunque entro 256mb (2^28 [26

bit + due zeri]) che è comunque

tanto. [Se proprio servisse andare oltre questo range, si usa un salto ad un

indirizzo che è contenuto in un registro {per mettere l’indirizzo in un registro si

usa la}].

Registro d’appoggio per pseudo istruzioni: per realizzare pseudo istruzioni,

l’assemblatore sfrutta istruzioni realmente esistenti: per esempio usa slt per

realizzare i salti con < >, ma necessita di un registro d’appoggio dove mettere il

registro settato di slt. Quindi esiste uno specifico registro che l’assemblatore utilizza

per questi scopi, in modo da non utilizzare altri registri che servono al

programmatore, non possono essere modificati dal programmatore. Il registro in

questione è il numero 1 ed è chiamato AT.

Gestione dello switch: Si pone un esempio di istruzione switch in C (4 casi). Come

gestirlo in modo semplice, senza verificare ogni condizione?

Es.

slt $t3, $s5, $zero # k <0?

bne $t3, $zero, Exit

slt $t3, $s5, $t2 # k >3?

beq $t3, $zero, Exit

add $t1, $s5, $s5

add $t1, $t1, $t1 # $t1=4*k

add $t1, $t1, $t4

lw $t0, 0($t1)

jr $t0 #vai a indir. letto →

L0: add $s0, $s3, $s4 #k=0, f=i+j

j Exit

L1: add $s0, $s1, $s2 #k=1, f=g+h

j Exit

L2: sub $s0, $s1, $s2 #k=2, f=g-h

j Exit

L3: sub $s0, $s3, $s4 #k=3, f=i-j

Exit:

Prima parte: controllo se k rientra nel range dei casi (tra 0 e 3), altrimenti salto tutto e

vado direttamente a Exit.

Seconda parte: questa è la parte che mi evita di controllare ogni condizione dello

switch: in precedenza è stata preparata una “tabella” in memoria nella quale, in ogni

cella, c’è l’indirizzo a cui saltare per ogni caso dello switch. Quindi, k viene

moltiplicato per 4 grazie alle due add e poi lo sommo a $t4 che è l’indirizzo base

della tabella. Ora carico il contenuto della cella della mia tabella in un registro (che è

l’indirizzo a cui devo saltare) e ci salto con jr.

Terza parte: grazie alla seconda parte, sarò saltato ad uno di questi “casi”.

Con poche righe evito di verificare tanti casi, la velocità è efficiente

indipendentemente dal numero di casi dello switch. L’unico “costo” è quello di

costruire la tabella. Il programma completo di creazione della tabella è sul sito del

prof denominato Case Switch (mi sembra).

I registri: per le operazioni vengono generalmente utilizzati i registri. I registri che

abbiamo a disposizione sono 32.

Quasi tutti sono modificabili, eccetto:

● Il return address menzionato prima ($ra)

● Il registro zero che contiene il valore zero, utile per le somme ($zero)

● Il registro numero 1 che è utilizzato dall’assemblatore come registro

d’appoggio per le pseudo-istruzioni

Gli altri registri possono essere richiamato con un nome secondo questa tabella (di

convenzioni)

I registri non preservati sono quelli utilizzabili liberamente dai sottoprogrammi. I

registri v sono usati per esempio per salvare un risultato di un sottoprogramma che

andrebbe ritornato al main.

Se un sottoprogramma sta usando i registri t ma gliene servono degli altri, può

utilizzare i registri s (che sarebbero modificabili solo dal main) ma prima di modificarli

deve salvare i valori che ci sono dentro agli s nello stack (con push) e prima di

tornare al main deve ripristinarli in modo che siano rimasti invariati per il main (con

pop).

Se il main sta usando i registri s ma gliene servono degli altri, può utilizzare i registri

t MA prima di passare ad un sottoprogramma dovrà LUI salvare il contenuto dei

registri t che ha usato nello stack perché il sottoprogramma potrà liberamente

utilizzarli e potrebbe sovrascrivere ciò che il main ha fatto. Il main li ripristinerà una

volta terminato il sottoprogramma.

Se da un sottoprogramma che usa solo registri t si passa ad un altro

sottoprogramma che usa solo registri t, il primo sottoprogramma dovrà salvare nello

stack i registri t che ha utilizzato prima di passare al secondo sottoprogramma

perché il secondo sottoprogramma può utilizzare i registri t liberamente e potrebbe

modificare ciò che ha fatto il primo sottoprogramma.

Problema Load/Store Byte: Pare esserci un problema quando si effettua la store

byte: si salva un solo byte (8 bit), quindi gli altri 26 bit rimangono aleatori. In realtà

questo problema non ha nessun effetto, perché quando si utilizzerà la load byte per

prelevare gli 8 bit dalla memoria, verrano appunto presi solo gli 8 bit che ci

interessano, ignorando i restanti 24 bit “sbagliati”. (Se usi lb nei 24 bit restanti si fa

l’estensione del segno, cioè si ripete il bit più significativo degli 8 validi, con lbu

vengono riempiti di 0)

Passare da lettera maiuscola a minuscola in ASCII: Generalmente si può sommare

20 visto che lettere maiuscole e minuscole sono a 20 di distanza nel codice ASCII (in

decimale o esa), ma questo richiede un po’ di istruzioni quindi si può utilizzare

un'altra tecnica: in binario, la lettera maiuscola e minuscola differiscono di solo un bit

(che nella maiuscola è 0 e nella minuscola è 1) [nella stessa posizione] allora si può

fare una or di quello specifico bit con 1 in modo da cambiarlo (oppure and per fare il

contrario).

Caricare una costante più grande di 16 bit: Con addi si potrebbe caricare massimo

un numero di 16 bit in un registro (perché è lo spazio che c’è nell’istruzione per quel

numero), e se si vuole caricare un numero di 32 bit? Si usa la lui per caricare la

parte alta del numero nel registro (e il resto diventa 0). Successivamente, con

l’istruzione di ori tra il contenuto attuale del registro (cioè solo la parte alta riempita e

il resto a zero) e la parte restante del numero, si vanno a riempire i 16 bit meno

significativi.

Es.

Parte 1) Riempio i bit più

significativi del registro con la

parte alta del numero, con lui, e il

resto viene settato a 0

Parte 2) Facendo ori tra il contenuto del registro e la parte restante del numero,

mettendo il risultato sempre nel registro,

riempio la parte restante

Nel codice basta anche fare li

$t0,numero anche se il numero è

maggiore di 16 bit, poi ci penserà l’assemblatore a trasformare il li in lui e ori.

Estensione del segno: Se si vuole “estendere” un numero in complemento a2, cioè

allungarlo, si estende il suo bit più significativo. Es: 0010 -> 0000 0010 sono

entrambi -2 oppure 1010 -> 1111 1010 è sempre -6. LO FANNO TUTTI QUELLI CON

FORMATO I TRANNE ORI ANDI XORI ECC… (OPERAZIONI SINGOLO BIT). E

ANCHE LBU CHE LO FA PER IL NUMERINO, E LW ECC….

Capire se c’è overflow con codice:

Per gestire l’overflow in autonomia, si usa la addu in modo tale che venga inibito il

segnale di overflow. Poi si applica un certo codice per capire se c’è stato overflow:

Se i segni dei due operandi sono diversi, sicuramente non c’è stato overflow, quindi

controllo se i segni dei due operandi sono uguali e se lo sono allora se il segno del

risultato è diverso da quello dei due operandi, c’è stato overflow. Per controllare se i

segni dei due operandi sono uguali, posso fare un'operazione di xor tra i due e poi

traslo di 31 il risultato in modo che mi rimanga solo il risultato della xor tra i due bit

più significativi (il segno) [lo xor da 1 se i due bit sono uguali mi sembra]. Un altro

modo per farlo è usare slt per controllare il segno degli operandi, di questo si può

vedere il codice:

li $t1, 0xf0001000

li $t2, 0xf0001000

#Rivelazione Overflow

addu $t0, $t1, $t2 #t0 = somma senza segnalare ow

xor $t3, $t1, $t2 #check dei segni di t1 e t2

slt $t3, $t3, $zero #segno < 0? t3 = 1 se segni diversi

bne $t3, $zero, no_ow #se t3 = 1 no overflow

xor $t3, $t0, $t1 #check dei segni di t1 e t0

slt $t3, $t3, $zero #segno < 0?

bne $t3, $zero, ow #t3 = 1 se segno somma diverso

no_ow: .....

ow: .....

Come rilevare il Carry con codice:

A+B si può fare? (Se non si può fare ci sarà riporto [valore assoluto (?)]). Posso

sommare ad A massimo (2^(32))-1-A <— ottengo quello scambiando 0 con 1.

li $t1, 0xf0001000

li $t2, 0xf0001000

#Rivelazione riporto

nor $t3, $t1, $zero #t3 = /(t1) = 2^32-1-t1

sltu $t3, $t3, $t2 #2^32-1-t1 < t2 ?

#->2^32-1 < t2 + t1?

bne $t3, $zero, carry

addu $t0, $t1, $t2 #no Carry: si può fare +

j poi

carry: .....

poi: ....

Problema memoria RAM continua a ROM:

Nel nostro simulatore la “””ROM”””, cioè lo spazio dove stanno le costanti è attaccata

alla RAM dove stanno i risultati, ma nel caso reale ci sarà la ROM poi dello spazio

vuoto non usato della ROM e poi la RAM. Non saranno attaccate.

Emulazione in tempo reale:

Per testare un processore, sarebbe

opportuno poter vedere come

cambia il contenuto dei suoi registri

interni, normalmente non si può fare.

Quindi si estrae il processore dalla

sua circuiteria e lo si inserisce in una

ICE (rimanendo collegato alla

circuiteria sua), il tutto è collegato al

sistema di sviluppo con periferiche

I/O.

Noi in realtà utilizzeremo un simulatore.

Interfaccia PC Spim:

In alto ci sono i registri numerati con il loro contenuto. Poi ci sono le istruzioni divise

in: indirizzo in cui sta l’istruzione, istruzione in esadecimale/binario, istruzione vera,

pseudo istruzione. Poi ci sono i dati della memoria centrale RAM. Poi c’è l’interfaccia

utente che segnala eventuali errori.

Simulator->settings->disabilita load exception file

File->open file .asm Simulator->set value e setti il PC con l’indirizz

Anteprima
Vedrai una selezione di 7 pagine su 26
Appunti di Architettura dei calcolatori Pag. 1 Appunti di Architettura dei calcolatori Pag. 2
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Appunti di Architettura dei calcolatori Pag. 6
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Appunti di Architettura dei calcolatori Pag. 11
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Appunti di Architettura dei calcolatori Pag. 16
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Appunti di Architettura dei calcolatori Pag. 21
Anteprima di 7 pagg. su 26.
Scarica il documento per vederlo tutto.
Appunti di Architettura dei calcolatori Pag. 26
1 su 26
D/illustrazione/soddisfatti o rimborsati
Acquista con carta o PayPal
Scarica i documenti tutte le volte che vuoi
Dettagli
SSD
Ingegneria industriale e dell'informazione ING-INF/01 Elettronica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher Patrik2205 di informazioni apprese con la frequenza delle lezioni di Architettura dei calcolatori e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi di Roma La Sapienza o del prof Burgio Giuseppe.
Appunti correlati Invia appunti e guadagna

Domande e risposte

Hai bisogno di aiuto?
Chiedi alla community