You are on page 1of 6

Stavamo vedendo i 3 modelli di architettura pensati dal punto di vista di quello

che avviene ai dati una volta presi dalla memoria e conservati dentro il
processore. Ieri abbiamo visto il modello ad accumulatore e stack. Lo stack ha
un vantaggio che consiste in un area di conservazione maggiore rispetto
allaria monocratica del processore ad accumulatore. Ha, insieme a quello
precedente, il vantaggio di avere un indirizzamento implicito del dato. Difatti
mentre nel 1 non potevo dare nessun tipo di specifica al dato perch
sottinteso ( mettilo direttamente nell accumulatore) anche nel 2 caso non
devo specificare nulla: Pop x, push x ( chiaro che il dato va messo nello
stack). Questo significa avere delle istruzioni pi compatte perch le istruzioni
di tipo aritmetico-logiche sono implementate nello stesso modo con cui sono
implementate nella macchina ad accumulatore, anzi ancora meglio se
vogliamo perch nellaccumulatore dicevo una somma e avevo un indirizzo nel
senso che facevo Accumulatore + y e il risultato andava in accumulatore. Nella
macchina a stack dico solamente ADD nel senso che il codice intende: prendi il
dato sullo stack, dopo di che ,sullo stack, dopo che ho preso quel dato c ne
un altro. Prendilo, fai la somma e il risultato lo rimetti nello stack. Lultimo dato
del mio stack sar quindi il risultato trovato precedentemente. In pratica ho
una struttura efficiente usabile per il calcolo delle espressioni matematiche. Per
esempio se abbiamo una espressione del tipo : (z*(y+3x-5)/q) ( risolviamo i
calcoli con maggiore precedenza).
Per cui questa espressione se la vado a scrivere correttamente in uno stack
funziona bene. Il sistema per funzionare bene tira fuori la x poi il * ( posso
implicitamente caricare anche le operazioni nella pila) e poi tiro fuori il 3 valore
che scrivo successivamente per continuare le operazioni. Sostanzialmente le
calcolatrici scientifiche ,quelle che gestiscono parantesi e altro, funziona
proprio con questo meccanismo, il loro processore gestisce i dati con modalit
stack. In pi gestendo le parentesi va a scrivere correttamente nello stack.
Andiamo ora avanti, capiamo che il limite di questa architettura che i dati,
anche se ne ho pi di uno, non vi posso accedere maniera casuale ( Che
significa? E diverso dal significato nel linguaggio casuale cio non prendere un
dato qualsiasi, ma nellinformatica significa che io posso accedere con una
certa volont in maniera del tutto indipendente dagli accessi che ho fatto in
precedenza. Ho cio la libert di prendere quel determinato dato senza nessun
collegamento con quello che ho fatto prima. Non come avveniva nello stack
dove sono vincolato a prendere il precedente o il successivo-> dipende dal
SP).
Avere pi dati allinterno del processore sicuramente una cosa positiva ma
sarebbe ancora meglio se tutti fossero accessibili in maniera casuale. Per fare
questo devo pensare ad un meccanismo pi complesso, avere una serie di
celle ciascuna che pu contenere un dato e avere la libert di accedere ad una
determinata di queste senza seguire un qualche ordine. Per fare ci devo poter
gestire un accesso a questa struttura ma devo anche (effetto collaterale)
pensare a specificare questi dati. Mentre prima dicevo POP e non avevo
bisogno di specificare nientaltro, qui non posso dire leggi e basta ma devo dire
leggi e cosa andare a leggere. Quindi una struttura di questo genere devo
specificare necessariamente gli operandi, e se una istruzione prevede 3
operandi, 2 sorgenti e uno di arrivo(destinazione) dovr necessariamente
specificarli tutti e 3.(nello stack non specificavo niente- nellaccumulatore solo
uno).facendo si, di conseguenza, che le istruzioni diventino pi lunghe
( immagina una macchina stack che fa 4 operazioni, basta 1 solo codice

operativo di 2 bit, in realt sar pi lunga perch prevede anche gli indirizzi,
per posso anche pensare che gli indirizzi sono nello stack). La macchina che
abbiamo appena descritto detta a registri ( le locazioni di prima sono i
registri) e quindi avremo un sistema che sar dimensionato dal numero di
registri e dalla lunghezza di parola ( processore a 32 registri per esempio).
In questa prima parte del corso penseremo a processori che lavorano solo su
dati interi ( dopo vedremo cosa aggiungere al processore per poter lavorare
anche con dati floating point).
Una macchina a registri deve prevedere una serie di strutture che complicano
anche lhardware per: gestire laccesso ai registri per esempio o un controllo
che dica quale dei registri deve andare in ingresso allALU quando deve fare
una operazione. Quindi ritornando al discorso fatto laltro giorno una macchina
a registri diventa LOAD/STORE nel momento in cui quella macchina fa si che le
operazioni interessano soltanto i dati che sono dentro i registri o al pi dati che
sono dentro i registri con leccezione che un operando sorgente possa anche
essere un operando scritto nellistruzione stessa.
Ho tre possibili locazioni di dove possono stare gli operandi: o in memoria o nei
registri o nellistruzione stessa (ovviamente non il risultato perch non ha
senso farglielo ricalcolare). Dal momento in cui il compilatore si accorge che nel
codice stesso vi il valore delloperando pu evitare di prendere quel valore e
scriverlo nel registro. ( Esempio del for) Quando scriver una istruzione al posto
di scrivere una somma di un certo registro r1 e un certo registro r2 e metto il
risultato in r1 posso dirgli direttamente di fare r1+1 senza sprecare memoria in
r2 e questo 1 lo metto direttamente nellistruzione. Non pi una istruzione
ADD ma ADDI(altro codice operativo). Qual la differenza? LALU deve fare
sempre un + per quando ho una istruzione non bisogna vederla ridotta ad una
sola operazione, ma va vista nel suo complesso poich prima di arrivare a fare
quel + il processore esegue altre operazioni. NellADDI uno dei dati in ingresso
allALU viene dato direttamente nellIR ed il numero 1, nella ADD per
prendere 2 dati in ingresso li devo entrambi prendere dai registri. Queste due
operazioni sono diverse anche se lALU fa sempre una somma. In pi non devo
pensare che per eseguire una istruzione non devo sempre pensare di usare
lALU dato che alle volte non viene neanche tirata in ballo.
Per fare unistruzione devo eseguire dei passi che chiameremo microistruzioni. Una istruzione composta da una sequenza di microistruzioni.
Quindi io posso immaginare una istruzione in cui ho un immediato scritto
nellistruzione stessa ( come nel LOAD/STORE dove limmediato era un pezzo
dellindirizzo).
Un altro modo di classificare i processori quello che sancisce il fatto che quel
processore abbia un set di istruzioni a lunghezza fissa o variabile. Cosa
significa? Per eseguire un programma lunit di calcolo esegue una sequenza di
istruzioni che va a pescare dalla memoria ( dove stanno scritte le istruzioni
caricate dal loader che quella parte di codice operativo che carica il
programma in memoria altrimenti il processore non pu leggerlo dato che non
pu leggere direttamente dallhard disk). Inoltre per leggere le istruzioni il
processore deve sapere quanti byte deve leggere ( posso avere unistruzione
da tot byte). In realt oggi esiste un solo approccio anche se di base sono 2
dato che in passato si discusso a lungo su questo determinato argomento.
1. Tutte le istruzioni hanno lunghezza costante ( le istruzioni sono tutte di 32
bit per esempio)

2. Un altro approccio dice di non vincolare tutte le istruzioni ad essere della


stessa lunghezza. Questo secondo approccio aveva un certo senso
quando si pensava ai processori di tipo CISC. Questo perch quel
processore pu essere tale da specificare un codice operativo, 2 operandi
sorgenti tramite due indirizzi in memoria e quello destinazione con un
indirizzo in memoria. Questa istruzione ( oltre codice operativo anche 3
indirizzi) richiede un certo numero di bit per avere un senso ( pochi non
conviene perch significa che indirizziamo una memoria molto piccola) e
quindi listruzione ha una certa lunghezza. Se ho per esempio la memoria
di 1MB lindirizzo di 20 bit quindi ho 60 bit per mettere questi 3
indirizzi. Se per esempio un indirizzo lo devo individuare prendendo
questo immediato e sommandolo al contenuto di un registro, dovr
anche dire qual il registro dove prendo la base. Nel processore ho 32
registri le parole sono di 5 bit( 32= 2^5). Quando specifico un registro
devo dare il nome di quel registro e devo dire quanto lungo quel
nome. In questo caso abbiamo 5 bit. Siamo arrivati a 78 bit pi un certo
numero per codificare il codice operativo. Quindi in una macchina CISC le
istruzioni possono avere una certa lunghezza. Se io ho quindi questa
macchina CISC con lunghezza costante listruzione far in modo tale da
prendere la pi lunga in modo da vincolare tutte le altre ad avere la
stessa lunghezza: c poi una istruzione che mi dice che ho finito il
programma. Questa istruzione prevede solo il codice operativo, non ho
bisogno di dire altro. Siccome ho scelto di fare lunghezza costante anche
questa avr 96 bit. Fondamentalmente quindi sto sprecando memoria.
Vantaggio lunghezza fissa: tutta la gestione del prelievo dell istruzione
semplificato. Tutte le istruzioni hanno quella lunghezza ( 32 bit per esempio)
li carico e so che mi sono caricato listruzione. Lo svantaggio che non
posso avere delle istruzioni pi lunghe oppure pu sprecando memoria ( se
per esempio metto 1 solo bit su 32)
Vantaggi lunghezza variabile: la lunghezza si adatta in base allistruzione
stessa. Chiaramente devo dare pur sempre dei tagli tipo 32-64-96 e poi le
istruzioni che riescono a stare in 32 vanno in 32, quelle da 64 in 64 e cosi
via. Ovviamente se ho una da 90 andr in 96 ma sprecher comunque solo
6 bit.
Una macchina di questo genere pu funzionare con 2 possibilit : essendo la
lunghezza variabile pongo in un colpo 96 bit di istruzione. Se ho una
memoria con una banda cos larga ( banda= la quantit di dati che posso
leggere o scrivere in una memoria in una unit di tempo). Se riesco in un
colpo di clock a leggere 96 bit va bene. Razionalmente andrebbe meglio
impostare 32 bit di memoria e se mi serve di pi leggo altri pezzi. Come
faccio a sapere se dopo aver letto 32 bit ho letto una istruzione intera d 32
bit o la prima di 2 puntate o la prima di 3 puntate. (Potrebbe finire con 00
per farci capire che finita listruzione ma sto comunque sprecando 2 bit in
pi potrebbe essere che un determinato dato finisca gi con 00 e quindi ci
sarebbe una incomprensione).
Fortunatamente il codice operativo dellistruzione comunque nella prima
puntata, se ho prelevato la prima parola ho comunque questo codice pi
una serie di informazioni ( operandi, indirizzi ecc) e se lunga o corta
implicita nel codice operativo. So quindi che istruzione ho e quanto lunga
e che magari devo andare a prendermi altri pezzi dalla memoria. E cosi che

gestisco luso di un processore che lavora con formato di parole a lunghezza


variabile, non sto sprecando molto bit .
Per cui se ho un ampia dinamica di istruzioni pu essere pi utile una
gestione a lunghezza variabile, se ho una dinamica abbastanza simile
(istruzioni pi o meno tutte lo stesso formato) meglio la lunghezza
costante. Va da s che le macchine CISC con un set complesso sono meglio
implementante con lunghezza variabile, macchine RISC con istruzioni
abbastanza simili come formato non hanno senso con logica a lunghezza
variabile ma meglio lunghezza costante.
Bisogna tenere presente che stiamo dimensionando un sistema e quando
compio questa operazione devo tenere conto di tutti quanti i parametri del
sistema . Bisogna fare un bilancio con tutti quanti i componenti.
Iniziamo ora una parte un po pi dettagliata: considero un processore RISC
perch abbiamo visto che se pur pu sembrare un limite per fare un
qualcosa necessita di pi istruzioni vedremo che il fatto che richiede queste
istruzioni ( codice pi lungo ovviamente) anche se sono pi compatte. Nel
cominciare a pensare a questo processore partiamo dal suo formato
istruzioni: un processore RISC con set di istruzioni a lunghezza costante,
definisco che la lunghezza costante di queste istruzioni 32 bit cio 4 byte e
definisco anche ( dato che questo numero non un problema a livello
tecnologico) 32 la dimensione della parola e la banda di memoria del
sistema stesso. Quindi le connessioni allinterno del processore e tra
processore e altri elementi un bus a 32 bit. Cosa un bus? Questo deriva
dalla parola latina omnibus ed un canale di comunicazione tra due
elementi. Supponiamo di avere due oggetti che possono essere connessi
che comunicano con un canale riservato. Invece quello che chiamiamo bus
non dedicato a far parlare solo due elementi ma usato per far parlare tra
di loro tanti elementi. E chiaro che ci sono vantaggi e svantaggi, possono
parlare in tanti ma in realt non contemporaneamente. Posso quindi
parlare con tutti dato che tutti potranno ascoltarmi ma solo uno potr
rispondermi. Ovviamente pi sono gli elementi collegati al bus pi esso
inefficiente. Quando scrivo devo dire a chi sto scrivendo, tutti gli altri
elementi possono leggere ma solo il diretto interessato potr rispondere.
Se sono stato prudente ho dato delle altre porte libere in modo in un futuro
di poter aggiungere un collegamento, ma anche il numero di porte libere
limitato. Con un bus puoi gestire in maniera adeguata un certo numero di
utenti in base al traffico richiesto da questi utenti.
Una volta determinata la parola abbiamo una serie di ridimensionamenti.
Questo processore per ora tratti dati interi, questi dati esistono in 4 formati:
- Dato word: 32 bit
- Dato long: 64 bit
- Dato half: 16 bit
- Dato byte: 8 bit
Questi dati vengono gestiti con e senza segno.
Per quanto riguarda la long sembra esserci un controsenso dato che
abbiamo stabilito come lunghezza 32, difatti il dato sar considerato in una
coppia di registri da 32 bit, cio siccome questo dato non sar frequente
quando avr bisogno di usare dati da 64 bit li user a puntate accoppiando
registri da 32 bit.
Viceversa i registri da 32 bit sono utilizzati per contenere dati da un byte o
da 2 byte sprecando quindi della memoria. Il numero di registri del nostro

processore 32 bit e ci significa che noi potremo specificare uno di questi


32 registri con 5 bit.
La lunghezza delle istruzioni di questo processore costante che per non
significa che le istruzioni abbiano tutte la stessa faccia perch nellambito
della lunghezza costante si avranno 3 diversi formati di istruzioni:
specificheremo col formato istruzione 3 modalit diverse di usare i 32 bit
con cui listruzione stessa codificata:
-I ( Immediato)
-J ( Jump)
-R (Registro)
Questi formati sono fatti nel seguente modo:
*Nelle istruzioni di tipo J occorre specificare un operando che puramente
immediato. Per questo avendo solo la necessit di specificare un immediato
tolti i 6 di codice operativo tutti i 26 li destineremo allimmediato.
*Nel caso di I ho 16 bit per limmediato mentre ho due da 5 bit per i registri.
Sono istruzioni che fanno uso di aritmetica in cui un operando immediato
(16)
*Nel caso di R ho un registro in pi. Gli altri ultimi 11 bit che avanzano sono
bit che servono a specificare meglio loperazione codificata nel codice
operativo e prenderanno il nome di campo funzione. (possiamo anche far
finta che questi 11 bit non ci siano)
Notiamo che J sono i salti incondizionati, noi in programmazione nel codice
che scriviamo usiamo salti condizionati del tipo se a>b fai questo altrimenti
questaltro. Dal punto di vista del flusso fai questo al posto di un altro si
codifica in una serie di operazioni dove nellultima parte ho un salto ad una
altra parte del programma ( si deve verificare una condizione affinch ci sia
possibile). Questo tipo di salto in letteratura prende il nome di brench.
Viceversa il Jump che il salto incondizionato, quando eseguo per
lappunto un salto senza pormi delle domande (ovvero avere delle
condizioni). Un esempio di salto incondizionato quando sono entrato in un
codice che esegue una routine, termina la routine e mi pongo il problema di
dove devo tornare.
Ovviamente devo tornare da dove provenivo e questo salto, come gi detto,
lo devo fare a prescindere. ( il salto codifica un indirizzo da dove tornare)
Prima di andare oltre vediamo di capire come gestito il ritorno da
subroutine.
Prima per la differenza tra funzione e procedura. Questi sono dei moduli del
mio algoritmo che io chiamo in questa maniera perch trattano una serie di
operazioni ripetitive. Se mi accorgo che c in pi punti del mio programma
qualcosa che si ripete decido di utilizzare la chiamata a quelle istruzioni
ripetute. La differenza filosofica che esiste tra quei due termini che mentre
la funzione restituisce qualcosa, la procedura non necessariamente
restituisce qualcosa. Entrambe possono modificare dei valori delle variabili
globali per se ho un blocco di istruzioni che produce un risultato e lo
restituisco non posso chiamarla procedura ma funzione.
Tra gli effetti collaterali della chiamata con ritorno di un qualcosa vi la
creazione di un numero che non sempre solo un numero ma pu anche
essere un vettore.
Nel linguaggio macchina sia procedure che funzioni prendono il nome di
subroutine, e sono alcune linee di codici codificate a parte che per nel mio
programma riesco a chiamare grazie ad una serie di istruzioni apposite.

Detto ci dobbiamo avere un meccanismo per gestire luso di queste


subroutine ( hanno senso solo se chiamate in pi punti del programma) e
soprattutto devo capire dove ritornare. Devo inoltre gestire unistruzione che
mi fa tornare da dove avevo chiamato che non pu essere un semplice Jump
allindirizzo k perch in questo modo la subroutine torner sempre
allindirizzo k. Per questa motivazione per gestire una situazione del genere
esiste una istruzione che specifica per questo. ( ovvero che mi permette di
tornare sia allindirizzo k+1 se chiamato da indirizzo k o j+1 se chiamato da
indirizzo j per esempio). Anche per le funzioni ricorsive si ha il discorso
appena fatto, vengono cio riconosciute come subroutine e bisogna vedere
come gestire il loro ritorno.
Provare a pensare come io riesco a gestire il ritorno di subroutine
per venerd.