Differenze tra le versioni di "Personal computer"

73 567 byte rimossi ,  14 anni fa
cancellato la parte spostata in più pagine
(Inizio a dividere in più pagine riorganizzando il materiale)
(cancellato la parte spostata in più pagine)
==Premesse che hanno portato all'idea di personal computer==
 
===Storia dei calcolatori===
La storia dei calcolatori è alquanto recente. Questo potrebbe apparire strano, considerato l'attuale boom tecnologico riguardante le tecnologie informatiche. Ma un calcolatore, almeno in origine, non prevedeva tutti gli usi che ne facciamo oggi. Il primo calcolatore elettronico, infatti, veniva impiegato per calcoli balistici durante la seconda guerra mondiale, progettato e costruito da J. P. Eckert e J. Mauchly venne denominato ENIAC, sigla per Electronic Numerical Integrator and Calculator, e la sua esistenza venne resa nota solo nel 1946. Lungo trenta metri, alto due e spesso uno, pesava trenta tonnellate per 120 metri cubi di volume su una superficie di 180 metri quadri, poteva eseguire 1900 somme al secondo e richiedeva una programmazione manuale attraverso fili ed interruttori in modo da riprodurre la rete logica dell'operazione da eseguire, i dati venivano poi introdotti tramite schede perforate.
 
Fu interessandosi al progetto ENIAC che John '''Von Neumann''' elaborò l'idea del calcolatore moderno, pensando alla memorizzazione dei programmi in forma numerica. Ne nacque la proposta di un calcolatore chiamato EDVAC, che, impropriamente attribuito al solo Von Neumann, vennere realizzato dagli ingegneri Eckert e Mauchly. La soluzione del calcolatore a programma memorizzato divenne subito la norma e distingue gli attuali calcolatori elettronici dalle macchine dedicate al calcolo. Il primo calcolatore ad avere un discreto successo fu in seguito l'[http://en.wikipedia.org/wiki/EDSAC EDSAC] (1949) che rimase popolare fino agli anni settanta.
 
L'IBM che in precedenza trattava macchine a schede perforate da ufficio, iniziò a costruire calcolatori a partire dal 1952 (IBM 701 di cui furono venduti 19 esemplari), ma fu l'introduzione del sistema S/360 a determinare il dominio dell'IBM in questo campo.
Per la prima volta si separavano il concetto di architettura da quello di realizzazione, questo permise all'azienda di definire un'organizzazione della macchina comune ad una famiglia di calcolatori che avrebbero potuto avere capacità, potenzialità ed anche dimensioni diverse pur rimanendo tra di loro compatibili, è il caso del sistema S/370 successore del precedente ma ad esso compatibile.
 
Tutti i calcolatori realizzati fra gli anni sessanta e settanta appartengono alla categoria dei ''mainframe'', ovvero macchine enormi e costose equipaggiate con sistemi operativi che ne permettevano un uso condiviso da parte di più utenti.
Solo nel 1965 la DEC introdusse il primo minicalcolatore, il PDP 8 al costo di 20.000 Dollari seguito nei primi anni settanta dal PDP 11, il primo minicalcolatore organizzato intorno ad un unico bus, un'organizzazione che diventerà uno standard per almeno vent'anni.
 
Il termine di ''microprocessore'' venne usato per la prima volta nel 1972 dalla Intel ed indicava un computer microprogammabile su un unico chip composto da una CPU (''Central Processing Unit'') integrata costituita da un sommatore parallelo a 4 bit, 16 registri, un accumulatore e uno ''stack''. Questa CPU venne denomintata 4004 ed utilizzata dal sistema MCS-4, nel frattempo la stessa IBM lavorava anche ad un microprocessore ad 8 bit, l'8008 introdotto nel 1972, interamente realizzato in tecnologia PMOS.
Fu però nel 1974, con l'introduzione dell'8080, che il microprocessore si mostrò in grado di essere utilizzato come elaboratore di uso generale (''general purpose''). Questo processore raggiungeva frequenze di clock di 2 MHz.
Il successo dell'8080 generò due microprocessori concorrenti, il Motorola MC6800 e lo Zilog Z80, il 6800 fu il primo processore ad utilizzare un'alimentazione singola di +5V abbassando notevolmente il costo del prodotto.
 
Il primo microprocessore con bus dati a 16 bit fu il PACE nel 1974, ma fu l'8086 della Intel, che indirizzava 1Mbyte di memoria contro i 64kbyte degli altri processori, a segnare il passaggio alla seconda generazione di microprocessori, con 5MHz di clock e 0,33 Milioni di istruzioni al secondo (''MIPS''). Ovviamente seguirono le versioni a 16 bit di Motorola e Zilog, rispettivamente MC68000 e Z8000, che, nonostante avessero prestaioni migliori, non contrastarono lo strapotere dell'Intel poichè all'uscita di questi processori, con quasi due anni di ritardo, l'Intel aveva già pronto l'8088 con bus a 8 bit.
L'Intel divenne quindi lo standard di fatto nel mondo dei PC mentre la Microsoft poneva le basi per divenrare la più grande compagnia software del pianeta.
Il Motorola MC68000 tuttavia ebbe un ruolo importante perchè si affermò sulle ''workstation'' (sistemi di carattere professionale) con sistema operativo Unix, questo processore era infatti più adeguato a gestire la memoria virtuale di Unix.
 
Mentre i processori a 16bit entravano in una fase di grande crescita con l'aggiunta di nuovi componenti avanzati quali i gestori di memoria (MMU), le unità aritmetiche in virgola mobile (FPU) e i controllori di accesso alla memoria (DMA), si assisteva alla nascita di nuova generazione di macchine dette RISC (''Reduced Instruction Set''), i vecchi calcolatori vennero denominati CISC (''Complex Instruction Set'').
La prima macchina RISC venne realizzata da IBM come prototipo (IBM 801) nel 1979 ma la prima realizzazione con un discreto successo è il RISC I nel 1982 seguita dal MIPS del 1983 realizzate rispettivamente dalle università di Berkeley e Stanford che convogliarono gli studi nello sviluppo delle CPU SPARC (''Scalable Processor Architecture'')
 
Intato nel 1984 Motorola introdusse il primo processore a 32 bit, l'MC68020 con frequenze di 25MHz. Questa volta fu Intel ad arrivare in ritardo introducendo il suo processore a 32 bit nel 1985, l'80386, ma questo non scalfì la sua egemonia, mantenuta grazie all'immenso patrimonio software ormai esistente.
L'architettura 68000 rimaneva un'alternativa valida soltanto nel campo delle workstation, ma i processori RISC stravolsero questo segmento di mercato. Agli inizi degli anni novanta IBM, Apple e Motorola formarono un consorzio per la produzione di una nuova CPU RISC per battere anche sul camo dell'informatica di basso costo lo strapotere dell'Intel, nacque quindi l'architettura ''PowerPC'' che si dimostrò essere quanto di meglio si potesse realizzare all'epoca. Nonostante ciò il PowerPC non riuscirà mai a contrastare la presenza di Intel nel campo dei Personal Computer.
 
===Architettura del calcolatore===
La concezione che abbiamo oggi di calcolatore multifunzione di tipo generale è basata su un concetto teorico di circa cinquanta anni fa. Von Neumann la elaborò come una macchina digitale ad esecuzione sequenziale e programma memorizzato, questo permette di esprimere il concetto di funzionamento desiderato in termini di sequenza di istruzioni (il programma) memorizzate su un supporto di memoria, in modo da poter cambiare funzionamento semplicemente cambiando il programma in esecuzione.
 
A costituire la struttura di un calcolatore sono:
* '''linguaggio macchina''' o ''Instruction Set Architecture - ISA'' ovvero un set di istruzioni che si pongono come interfaccia all'utente per le operazioni che svolge il calcolatore
* '''struttura interna''' l'insieme di blocchi interconnessi, ognuno costituente un sottosistema logico della macchina
* '''realizzazione circuitale''' ovvero la tecnologia microelettronica alla base del sistema
 
L'architetttura risulta adatta a trattare problemi molto pù complessi di quelli delle reti logiche, per questo motivo il calcolatore è suddiviso in blocchi più semplici collegati da una struttura ad hoc.
Gli elementi indipendenti sono le strutture di
* '''memoria principale''', contiene le istruzioni del programma da eseguire e i risultati delle operazioni svolte. Istruzioni e dati sono codificati in forma binaria, questo comporta che non siano di per sè distinguibili le istruzioni dai dati, ma tutto dipende dal modo in cui un gruppo di bit viene letto e interpretato. Il fatto che dati e istruzioni sono diano distinguibili permetti di avere programmi, quali i compilatori, che generano dati che possono essere interpretati come istruzioni.
* l' '''unità di elaborazione''' e controllo o '''CPU''' (''Central Processing Unit'') è quella che interpreta ed esegue il programma, sarà a sua volta suddivisa in sottosistemi più semplici quali PC (''Program Counter''), ALU (''Arithmetic and Logic Unit''), Registri, Cache e vari strumenti aggiuntivi,
* Le '''Interfacce''' o ''registri di Input/Output'' con il compito di interfacciarsi ai dispositivi di input/output (memoria di massa, monitor, tastiera, stampante, ...); sono detti anche buffer.
 
A permettere la comunicazione, e quindi lo scambio dati, tra questi tre blocchi vi è il '''Bus di sistema'''. Fino agli anni novanta il bus era un unico canale di comunicazione, con l'enorme crescita delle prestazioni della CPU e l'impiego di memorie ''cache'' la struttara a bus unico venne abbandonata per far spazio alle attuali architetture che prevedono più bus. Vi è quindi un bus ad altissima velocità per il trasferimento di dati tra memoria cache, memoria centrare e CPU, un bus a prestazioni più basse che collega i dispositivi periferici.
 
Il moderno sviluppo dell'informatica sta avvenendo soprattutto nell'ambito dei processori. Resta infatti la struttura di comunicazione, detta BUS, una delle parti più lente della macchina. Approssimativamente, possiamo dire che mentre i più recenti processori hanno frequenze di Clock che vanno dai 4 ai 6 GHz, la frequenza del clock del BUS, non va, nei computer a più alte prestazioni, oltre gli 800 MHz. Una tale differenza provoca inevitabilmente dei colli di bottiglia. L'unica altra parte del computer che è più lenta è il disco rigido.
 
Abbiamo messo in un gruppo tutti i tipi di memoria, sia contenente dati che programmi. Questo perché se si prende un singolo byte, non si può dire se esso provenga da un programma o da un dato.
 
Da un punto di vista logico si può dividere il BUS di sistema in tre bus:
* Bus Dati, ha il parallelismo della cpu e scambia istruzioni e dati con gli altri ''bus agent'', ovvero memoria e I/O
* Bus di controllo, contiene informazioni che permettono all'agente ''master'' (la CPU) il controllo degli agenti ''slave'' (memoria e interfacce), quali ad esempio il ''Memory Read Command'', ''Memory Write Command'', ...
* Bus di indirizzi, ha la dimensione dello spazio di indirizzamento della memoria (l'8086 aveva 20 bit di indirizzo, gli attuali calcolatori hanno 32 o anche 36 bit)
 
==== CPU ====
 
Rappresentando la CPU con un diagramma compatto la si può dividere in due semplici stati, quello di: '''IF''' e quello di '''EX''', dato che l'unico compito che la CPU deve svolgere è eseguire le istruzioni.
I due stati sono quindi '''I'''nstruction '''F'''etch ed '''EX'''ecute.
Quando la CPU si trova nello stato IF legge dalla memoria l'istruzione che eseguirà al prossimo ciclo di clock nello stato di EX.
L'esecuzione di un programma, ovvero una sequenza di istruzioni, richiede che esista un registro speciale con funzione di puntatore alla prossima istruzione da eseguire. Tale registro è il '''P'''rogram '''C'''ounter (PC).
Nello stato di Fetch la CPU legge nella cella di memoria indirizzata dal PC l'istruzione da eseguire e prima di passare allo stato di Execute aggiorna il PC con l'indirizzo della prossima istruzione da eseguire. Il PC può essere aggiornato anche quando la CPU si trova nello stato di Execute, è il caso dell istruzioni di trasferimento del flusso, ovvero di istruzioni che modificano il normale comportamento sequenziale della macchina per effettuare salti tra istruzioni non contigue (in linguaggi di programmazione più alti, quali il C, corrispondono concettualmente alle istruzioni di controllo condizionato).
 
 
La CPU è inoltre diviso in due unità fondamentali:
* '''unità di calcolo'''
* '''unità di controllo'''
 
===== Unità di controllo =====
 
L'unità di controllo è una rete sequenziale sincrona comandata dai comandi di ''ready'' ed ''interrupt'' che ha il compito di generare, in base alle istruzioni in ingresso dalla memoria, l'unità di calcolo, specificando una determinata ''micro-operazione'', ovvero un'operazione eseguita all'interno dell'unità di calcolo in un ciclo di clock, quale ad esempio il trasferimento di un dato da un registro ad un altro o una elaborazione della ALU
 
===== Unità di calcolo =====
 
L'unità di calcolo, o anche '''Datapath''' contiene tutte le unità di elaborazione ed i registri necessari per l'esecuzione delle istruzioni della CPU, ogni operazione è una sequenza di operazioni elementari, le micro-operazioni.
 
L'unità di calcolo differisce molto tra diverse architetture ma gli elementi fondamentali sono:
* ''Instruction Register'' IR, è il registro che contiene l'istruzione in corso di esecuzione, viene caricato nella fase di fetch e determina le azioni svolte durante la fase di esecuzione
*''Program Counter'' PC, il registro che tiene traccia dell'esecuzione del programma
*''Memory Address Register'' MAR è il registro che contiene l'indirizzo della locazione di memoria da leggere o scrivere. L'uscita di questo registro è sul Bus degli Indirizzi
*''Memory Data Register'' MDR è il registro attraverso la quale viene scambianta l'informazione tra la memoria e la CPU, la sua uscita è sul Bus Dati
*''Registri General Purpose'' di uso generale, sono impiegati per memorizzare dati su cui opera la ALU, sono quindi utilizzati sia di appoggio dati che per gestire l'indirizzamento. Nelle CPU più datate questi registri erano più specializzati, vi era in particolare il registro ''accumulatore'' che doveva essere necessariamente utilizzato in certe operazioni aritmetico logiche.
 
 
===== Quantifiche Prestazionali dei sistemi =====
Come abbiamo già visto esistono vari tipi di sistemi, ognuno con architetture specifiche e metodi di produzione diversi e ognuno con prestazioni differenti. Per poter fare confronti si deve cercare di creare una scala di misurazione. Su quest'argomento si dibatte costantemente. Ecco le principali unità di misurazione, ricordando sempre che i valori devono essere calcolati utilizzando dei benchmark comuni, ovvero dei benchmark sviluppati con un linguaggio di alto livello per poi essere compilati indipendentemente per processori diversi. Questo implica un diverso numero di istruzioni semplici.
 
======CPU-time======
Questo tipo di misurazione si basa sul mero tempo necessario per eseguire un programma.
 
:<math>CPU_{time}=N_{istruzioni}\cdot CPI_{medio} \cdot T_{ck}</math>
 
con
*<math>N_{istruzioni}</math> pari al numero di istruzioni che compongono il programma in quella particolare architettura.
*<math>CPI_{medio}</math> pari al numero di cicli di clock mediamente necessari per ogni istruzione semplice.
*<math>T_{ck}</math> pari al tempo fisico di durata di un singolo ciclo di clock.
 
Il concetto di <math>CPI_{medio}</math> è piuttosto complesso. Infatti osservando che una certa istruzione può non essere eseguita nello stesso tempo di una'altra istruzione, si deve proprio calcolare il tempo medio di esecuzione dell'istruzione. In particolare se ne calcolerà la media ponderale sulle n istruzioni necessarie per l'esecuzione del programma.
:<math>CPI_{medio}=\sum_{i=1}^{n} (CPI_i \cdot \frac{N_i}{N_{tot}})</math>
 
Il valore di CPI medio, teoricamente, dovrebbe sempre essere maggiore di 1, ma non è proprio così, in quanto oltre all'esecuzione consecutiva, in un processore si hanno anche, soprattutto nelle ultime generazioni di processori, alti livelli di parallelismo. Questo significa che più istruzioni possono essere eseguite contemporaneamente. Perciò spesso si hanno <math>CPI_{medie}</math> pari a 0.5.
 
======MIPS======
Questo tipo di unità di misura piace molto ai giornalisti, ma non ha grande rilevanza scientifica. Infatti rappresenta i milioni di istruzioni al secondo, calcolate rispetto ad un generico <math>CPI_{medio}</math> basato su un benchmark. Quindi
:<math>MIPS=\frac {f_{ck}}{CPI_{medio}}</math>
Questo sarebbe un buon risultato se non fosse che a parità di benchmark e MIPS possono risultare CPU-time e N diversi, per la stessa architettura.
 
Per questi motivi l'acronimo MIPS è stato negli anni scherzosamente ridefinito come Meaningless Indication Provided by Salesman, ovvero Indicazione con scarso significato fornita dai venditori.
 
======Speed-Up======
Questa scala permette di osservare se a parità di benchmark un sistema è più veloce di un altro. Il problema sta nel fatto che il calcolo è talmente approssimativo che in realtà un valore basso di speedup non rappresenta una certezza da un punto di vista di velocità di esecuzione. Infatti si ha
<math>SU_{A/B}=\frac {CPU_{time}(B)}{CPU_{time}(A)}</math>
che al variare di alcuni aspetti del benchmark e dell'uso di certe istruzioni può completamente ribaltare ogni risultato ottenuto con un tale tipo di calcolo.
 
==Gli Standard alla base di un personal computer==
 
===La corretta dissipazione del calore===
 
 
 
=Linguaggio Assembler=
Il linguaggio macchina è una sequenza di bit e la sua rappresentazione simbolica è l''''assembler''', costituisce il livello dell'architettura visibile agli sviluppatori dei compilatori.
 
Le istruzioni del linguaggio macchina devono permettere di utilizzare le risorse interne del calcolatore, e quindi di accedere alla memoria, ai registri interni alla cpu e di eseguire delle operazioni, per le diverse implementazioni di questo aspetto, i linguaggi macchina sono classificabili in base a
* Memorizzazione dell'operando nella CPU
* Numero di chiamate esplicite agli operandi per istruzione
* Collocazione degli operandi
* Indirizzamento alla memoria
* Operazioni
* Tipologia e dimensione degli operandi
 
Supponiamo di voler eseguire una semplice operazione quale una somma tra due numeri (A e B) e di voler salvare il risultato in un registro interno alla cpu, il linguaggio macchina deve quindi fornire una codifica per l'operazione che voglio eseguire e gli operandi (indirizzo dei valori in ingresso e del registro in uscita) per cui si può generalizzare indicando il seguente formato per una istruzione assembler:
 
''etichetta'' ''codice_mnemonico'' ''operandi''.
 
L'''etichetta'' è un parametro opzionale e rappresenta in modo simbolico l'indirizzo dell'istruzione; è utile per la gestione del flusso;
 
il ''codice_mnemonico'' è un identificativo per il codice operativo;
 
''operandi'' è una lista di operandi separati di virgola, il primo dei quali è quello di destinazione
 
== Memorizzazione dell'operando nella CPU ==
 
In generale tutte le architetture sono classificabili in base ai tre tipi di approccio:
*''Stack Architecture'': Tutti gli operandi sono sullo stack; in particolare una qualsiasi istruzione usa come operandi sorgente gli ultimi due sullo stack, li cancella e mette l'operando risultato in cima allo stack
*''Accumulator Architecture'': Uno dei due operandi sorgente è sempre il valore di un registro speciale detto ''accumulatore''; il risultato dell'operazione viene messo di nuovo sull'accumulatore
*''General-Purpose Register (GPR) Architecture'': gli operandi risiedono tutti su registri interni alla CPU oppure in memoria.
 
Vantaggi e svantaggi dei diversi tipi di approcci sono legati alla misura in cui la macchina si adatta alle necessità del compilatore ed a quanto risulta efficiente il codice rispetto alle altre metodologie. Per questo motivo tutte le macchine moderne utilizzano registri GPR, nel qual modo vengono velocizzate tutte le operazioni. Il tempo di accesso ad un registro è inferiore rispetto a quello della memoria e viene pure fornito uno strumento generale di memorizzazione più semplice da usare.
 
Da notare inoltre che gli altri due approcci non consentono l'accesso casuale e la gestione di pipeline, ad esempio l'esecuzione dell'operazione (A*B)-(C*D) deve essere eseguita sequenzialmente, mentre utilizzando i registri è possibile eseguire le operazioni tra parentesi in qualsiasi ordine e quindi anche in pipeline.
 
Va comunque detto che, nonostante l'affermazione delle macchine GPR, esistono degli "ibridi": proprio l'Intel 8086 è una via di mezzo tra una macchina GPR ed una ad accumulatore.
 
== Numero di chiamate esplicite agli operandi per istruzione ==
 
Il numero degli operandi che sono chiamati in modo esplicito in una tipica istruzione dipende fortemente dal tipo di memorizzazione.
Ad esempio l'istruzione C = A + B può essere tradotta nelle tre categorie di insiemi di istruzioni:
 
'''Pila'''
 
PUSH A Metti A nello stack
 
PUSH B Metti B nello stack
 
ADD Esegui la somma tra gli ultimi due valori nello stack
 
POP C Prendi C dallo stack
 
 
'''Accumulatore'''
 
LOAD A Carica A sull'accumulatore
 
ADD B Aggiungici B
 
STORE C Salva in C il valore dell'accumulatore
 
 
'''Registro'''
 
LOAD R1,A Inserisci A nel registro R1
 
ADD R1,B Somma il registro R1 con B e salva il valore in R1
 
STORE C,R1 Inserisci il valore di R1 in C
 
 
 
In questo esempio il numero di operandi espliciti per la sola operazione di ADD è rispettivamente 0,1,2 - ma nel caso di macchine GPR può anche essere tre. Infatti, nell'esempio considerato l'istruzione ADD R1,B esegue la somma tra R1 e B salvando il risultato nella posizione del primo operando. Ma per certi Instruction Set esistono anche operazioni del tipo ADD R1,R2,R3 a seguito della quale la somma tra i valori di R2 ed R3 viene salvata nel registro R1.
 
Riassumendo quindi il numero di operandi espliciti per un'istruzione della ALU è compreso tra 0 e 3.
 
== Collocazione degli operandi ==
 
Oltre alla presenza di due o tre operandi in una istruzione ALU, un'altra caratteristica che differenzia le architetture GPR è il numero di operandi in memoria consentiti in una tipica istruzione che può variare da zero a tre.
 
Le possibili combinazioni di numero di operandi e numero di indirizzamenti a memoria sono sette, ma bastano tre per classificare quasi tutte le macchine esistenti:
* ''register-register'' (3,0)
* ''memory-register'' (2,1)
* ''memory-memory'' (3,3)
''La coppia tra parentesi indica il (numero di operandi espliciti di un'istruzione ALU), (numero di operandi risiedenti in memoria)''
 
Le macchine R-R (register-register) hanno il vantaggio di codificare più semplicemente le istruzioni (in quanto a lunghezza fissa) che risultano anche essere più corte (lo spazio dei registri è indirizzabile con meno bit rispetto a quello della memoria) tuttavia questo comporta un più alto numero di istruzioni per riferimenti in memoria.
D'altro canto le M-M non sprecano registri per memorizzare temporaneamente i dati ma presentano un collo di bottiglia nella velocita di accesso alla memoria.
 
 
== Indirizzamento alla memoria ==
 
Indipendentemente dal tipo di architettura a registri o con riferimenti alla memoria, è necessario definire il metodo di interpretazione degli indirizzi di memoria e la loro specifica.
 
=== Interpretazione degli indirizzi di memoria ===
 
Esistono due diverse convenzioni per ordinare i byte all'interno di una parola:
* '''Little Endian''' ordinamento a partire dal byte meno significativo, l'indirizzo di un dato è quello del suo byte meno significativo
* '''Big Endian''' ordinamento a partire dal byte più significativo, l'indirizzo è quindi quello del suo byte più significativo.
 
La scelta di una delle due tecniche è del tutto convenzionale visto che nessuna presenta vantaggi/svantaggi rispetto all'altra, ed è possibile applicare la stessa tecnica di posizionamento anche ai bit nonostante poche architetture dispongano di istruzioni che accedono direttamente ad essi.
Un esempio di microprocessore Little Endian è un qualsiasi Intel della famiglia 80x86; i microprocessori Motorola sono invece Big Endian.
 
Ben più importante è l''''allineamento'''.
Alcune macchine (ormai quasi tutte) richiedono che l'accesso ad un oggetto di ''s'' byte, collocato all'indirizzo A, sia allineato con l'indirizzo, ovvero la divisione tra A ed ''s'' dia resto 0.
Ad esempio una parola da 4 byte richiede che il suo indirizzo sia multiplo di 4.
 
Sebbene possa sembrare una limitazione, l'allineamento rende l'esecuzione dei programmi più veloce.
 
 
=== Modalità di Indirizzamento ===
 
La modalità di indirizzamento è il modo in cui le architetture specificano l'indirizzo degli oggetti a cui accedere, che possono essere una costante, un registro o una locazione di memoria.
Le modalità principali sono:
 
* '''Registro''' ADD R4,R3
 
Il valore dell'indirizzo è un registro
 
* '''Immediato''' ADDI R4,10
 
Il valore dell'operando sorgente è una costante
 
* '''Offset''' ADD R4,100(R1)
 
Accede a variabili locali, l'indirizzo dell'operando sorgente è calcolato come somma di 100 ed il valore di R1.
 
 
Le modalità di indirizzamento possono ridurre anche sensibilmente il numero di operazioni da effettuare a discapito di una maggiore complessità della macchina.
 
 
== Operazioni ==
 
Gli operatori supportati dalla maggior parte degli Instruction Set possono essere raggruppati nelle seguenti categorie:
 
* '''Aritmetico / Logiche''': Operazioni aritmetiche (somma, sottrazione...) o logiche (and, or...).
* '''Trasferimenti di dati''': Istruzioni di trasferimento per macchine con indirizzamento in memoria.
* '''Controllo''': Diramazione (''branch''), salto (''jump''), chiamata a procedura (''call''), trappole (''trap''), permettono il controllo del flusso di esecuzione di un programma modificando il valore del Program Counter (PC, contiene il valore dell'indirizzo della prossima istruzione da eseguire).
* '''Sistema''': Chiamate al sistema operativo, istruzioni per la gestione della memoria virtuale.
* '''Virgola mobile''': Operazioni con numeri rappresentati in virgola mobile (''floating point'').
* '''Stringa''': Spostamento di stringhe, confronto fra stringhe, ricerca su una stringa.
 
== Tipologia e dimensione degli operandi ==
 
Solitamente il tipo stesso di un operando fornisce la sua dimensione.
I tipi più comuni sono il carattere (1 byte, 8 bit), mezza parola (16 bit), parola (32 bit), virgola mobile in singola precisione (32 bit) o in doppia precisione (64 bit). I caratteri vengono rapresentati nel formato EBCDIC (IBM) o più frequentemente in ASCII (attualmente anche UNICODE). I numeri in virgola mobile sono ormai quasi tutti rappresentati seguendo lo standard IEEE 754 anche se in precedenza ogni costruttore sceglieva una propria rappresentazione.
 
I numeri naturali vengono rappresentati con numeri binari in complemento a due.
 
=== Rappresentazion in complemento a due ===
Dato un numero binario qualsiasi ad n bit (<math>b_{n-1} b_{n-2} ... b_{1} b_{0}</math>), il valore decimale lo si calcola come
<math>N= 2^{n-1} * b_{n-1} + 2^{n-2} * b_{n-2} + ... + 2^1 * b_1 + 2^0 * b_0</math>
Tuttavia con questa rappresentazione sono rappresentabili soltanto numero positivi.
 
Una rappresentazione alternativa è quella in complemento a 2 che permette con un numero binario a n bit un numero decimale compreso tra <math>-2^{n-1}; +2^{n-1}-1</math>.
Con questa rappresentazione il bit di maggior peso è quello di segno (0 indica un numero positivo, 1 uno negativo) e il valore decimale può essere calcolato come:
<math>N= -2^{n-1} * b_{n-1} + 2^{n-2} * b_{n-2} + ... + 2^1 * b_1 + 2^0 * b_0</math>.
Questa operazione permette di eseguire la sottrazione di due numeri qualsiasi con un sommatore, infatti A-B è equivalente alla somma tra A e il complemento a 2 di B.
Un numero negativo si ottiene anche sommando 1 alla negazione del numero in valore assoluto, per cui se con n=4 bit posso rappresentare il numero 6 come 0110, -6 in complemento a due sarà 1 + not(0110) = 1 + 1001 = 1010.
 
=DLX=
L'architettura DLX è molto semplice, se confrontata con i moderni processori. E' di tipo MIPS, come lo erano i primi Sparc.
 
L'uso della memoria è molto semplice, in quanto il processore non possiede la Stack, ma un solo registro. Non ha nemmeno Flag, quindi sono necessari a livello di OpCode comandi che permettano di proporre condizioni sull'esecuzione di certi blocchi di codice. Non avendo nessuna struttura esterna per il mantenimento "on the fly" di dati importanti per il processore, non si può fare che un jump alla volta. L'indirizzo di partenza viene infatti salvato nell'ultima casella del registro: R31. (Ovviamente stiamo parlando di un processore Big-Endian, in quanto l'ultima casella è anche la più "grande" in indice).
 
Le istruzioni sono allineate a multipli di 4
 
La memoria è composta da un solo registro con 32 blocchi di 32 bit ciascuno, e il processore richiede l'allineamento. Ha anche 32 registri per numeri in virgola mobile.
 
Da un punto di vista operativo le operazioni possibili sono di tre tipi, considerando che è un processore a 32 bit fissi, ovvero ogni istruzione è composta da 32 bit, compresi OpCode e tutti gli eventuali argomenti.
 
Da un punto di vista logico, invece, i tipi di operazioni sono quattro:
* aritmetico logiche
* load e store
* diramazione e salti
* virgola mobile
 
Ogni registro può essere caricato e memorizzato ad esclusione del solo R0 che ha il valore costante 0.
 
L'unica modalità d indirizzamento possibile è quella tramite offset, ma con offset R0 è possibile ottenere l'indirizzamento diretto.
 
I caricamenti di mezze parole o byte mettono il valore nella parte bassa del registro ed azzerano la parte alta (oppure estendono il bit di segno).
 
Tutte le operazioni ALU sono di tipo Register-Register.
Sono permessi gli operandi immediati, ovvero valori forniti direttamente dal codice Assembler e non presenti in registri o memorie, l'uso è limitato al terzo operando esclipito e viene indicato nello mnemonico dell'operazione con l'aggiunta del suffisso I al mnemonico dell'operazione da eseguire (ad esempio ADDI R3,R3,5)
 
Il controllo del flusso del programma avviene tramite salti e diramazioni, i primi possono essere con collegamento (memorizzano l'indirizzo dell'istruzione successiva a quella del salto per riprendere il flusso) o meno; le diramazioni sono tutte con condizione da verificare.
 
 
==Formato operazioni==
Ogni OpCode è composto di 6 bit, ogni indirizzo di 5, mentre i restanti bit sono eventualmente per offset o altri tipi di input. Le tre grandi famiglie di comandi sono:
 
[[Immagine:I-format.gif|right|Formato I]] Formato I: Questo formato è quello utilizzato per ogni operazione con operatori immediati. Come si nota l'offset è di soli 16 bit, il che significa che non si può passare come argomento direttamente un dato a 32 bit, bensì bisogna suddividerlo in due passaggi con in mezzo uno shift di 16 posizioni.
 
[[Immagine:R-format.gif|left| Formato R]] Formato R: Questo è il formato delle operazioni fra registri. Gli 11 bit finali vengono utilizzati per rendere più specifiche alcune operazioni (ricordiamo che essendoci solo 6 bit per le operazioni, il numero massimo di queste è <math>2^{6}=64</math>)
 
[[Immagine:J-format.gif|right| Formato J]] Formato J: Quest'ultimo è il formato dei salti condizionati o meno.
 
== Operazioni ==
*Trasferimenti di dati
**'''LB, LBU, SB '''Carica un byte, carica un byte senza segno, memorizza un byte
**'''LH, LHU, SH''' Carica halfword, carica halfword senza segno, memorizza halfword
**'''LW, SW''' Load word, store word (a/da registri integer)
**'''LF, LD, SF, SD''' Load SP float, load DP float, store SP float, store DP float (SP - single precision, DP - double precision)
**'''MOVI2S, MOVS2I''' Move from/to GPR to/from a special register
**'''MOVF, MOVD''' Copy one floating-point register or a DP pair to another register or pair
**'''MOVFP2I, MOVI2FP''' Move 32 bits from/to FP tegister to/from integer registers
 
*Aritmetica/Logica
**'''ADD, ADDI, ADDU, ADDUI''' Add, add immediate (tutti gli immediati sono a 16 bit); signed and unsigned
**'''SUB, SUBI, SUBU, SUBUI''' Subtract, subtract immediate; signed and unsigned
**'''MULT, MULTU, DIV, DIVU''' Multiply and divide, signed and unsigned; Gli operandi devono essere registri Floating-point; Tutte le operazioni richiedono valori a 32 bit e danno come risultato valori a 32 bit.
**'''AND, ANDI''' And, And immediate
**'''OR, ORI, XOP, XOPI''' Or, or immediate, exclusive or, exclusive or immediate
**'''LHI''' Load high immediate - carica nella parte alta del registro il dato immediato
**'''SLL, SRL, SRA, SLLI, SRLI, SRAI''' Shifts: sia immediato(S__I) che variabile (S__); gli shift sono Shift Left Logical, Right Logical, Right Arithmetic
**'''S__, S__I, S__U''' Set condizionati: "__" può essere LT lesse than, GT greather than, LE less-equal, GE greater-equal, EQ equal, NE not equal
 
*Controllo
**'''BEQZ, BNEZ''' Branch GPR equal/not equal to zero; 16-bit offset from PC
**'''BFPT, BFPF''' Test comparison bit in the FP status register and branch; 16-bit offset from PC
**'''J, JR''' Jumps: 26-bit offset from PC(J) or target in register(JR)
**'''JAL, JALR''' Jump and link: save PC+4 to R31, target is PC-relative(JAL) ot a register(JALR)
**'''TRAP''' Transfer to operating system at a vectored address
**'''RFE''' Return to user code From an Exception; restore user code
 
*Virgola Mobile
**'''ADDD, ADDF''' Add DP, numeri SP
**'''SUBD, SUBF''' Subtract DP, numeri SP
**'''MULTD, MULTF''' Multiply DP, SP floating point
**'''DIVD, DIVF''' Divide DP, SP floating point
**'''CVTF2D, CVTF2I, CVTD2F''',
**'''CVTD2I, CVTI2F, CVTI2D''' Istruzioni di Conversione: CVTx2y converte dal tipo ''x'' al tipo ''y'', dove x e y sono I(Integer), D(Double precision), o F(Single precision). Entrambi gli operandi sono nel registro FP
**'''__D, __F''' confronti DP e SP: "__" può essere LT, GT, LE, GE, EQ, NE; impostare il bit di paragone nel registro FP.
 
=== Istruzioni aritmetiche e logiche ===
 
Sono istruzioni a tre operandi, il primo è il registro ''destinazione'', gli altri due registri ''sorgente''
 
Ad esempio
'''ADD R1, R2, R3''' eseguel l'operazione ''R1 = R2 + R3''
'''ADDI R1, R0, 100''' esegue ''R1 = 100'' (R0 vale sempre 0 e non è modificabile)
'''ADD R1, R2, R0''' esegue ''R1 <- R2'' (assegna ad R1 il valore di R2)
 
Le istruzioni di set condizionato ( S__ ) eseguono un confronto tra i due operando sorgente ed assegnano il valore 0 oppure 1 a seconda del risultato del contronto, per cui
 
'''SNE R1, R2, R3''' assegnerà il valore 1 ad R1 se R2 ed R3 non sono uguali, in caso contrario assegnerà il valore 0
 
Le operazioni di confronto possono essere eseguite anche tra un registro e un operando immediato
 
'''SLTI R1, R2, 10''' assegna 1 ad R1 se R2 è più piccolo di 10
 
o tra operandi senza segno (e non in complemento a due)
 
'''SGTU R1, R2, R3''' assegna 1 ad R1 se R2 è più grande di R3
 
 
=== Istruzioni di trasferimento dati ===
 
Dato che il DLX è un Instruction Set R-R prima di utilizzare dei valori presenti in memoria deve copiarli in un registro interno alla CPU per questo motivo esistono operazioni di prelievo e scrittura di dati da e verso la memoria.
 
Sono istruzioni codificate nel formato I per cui richiedono tre operandi espliciti, nella forma
''destinazione'', ''offset''(''registro''). L'indirizzo di memoria a cui accedere sarà calcolato come la somma del valore in ''registro'' e dell' ''offset'', ad esempio
 
'''LW R1, 100(R2)''' preleva una parola ( Load Word ) di trentadue bit dall'indirizzo di memoria 100 + R2 e memorizzala in R1, operazione che si può anche scrivere come ''R1 <-32 M[100+R2]''
 
Analogamente
'''SW R1, 20(R3)''' salverà nell'indirizzo di memoria 20 + R3 la parola (Store Word) contenuta nel registro R1
 
 
=== Istruzioni di trasferimento del controllo ===
 
Nel DLX queste istruzioni sono di tre tipi:
* '''Jump''' Effettua un salto, un trasferimento del controllo, incondizionato
* '''Jump And Link''' Trasferisce il controllo salvando l'indirizzo di ritorno nel registro R31 (si parla anche di ''chiamata a procedura'')
* '''Branch''' Trasferisce il controllo soltanto dopo aver verificato una condizione ( ''salto condizionato'' )
 
Le istruzioni di Jump e Jump And Link possono essere sia di tipo I che J
 
Jump con istruzione di tipo '''I'''
 
'''J R3''' Modifica il program counter con il valore del registro R3, ''PC <- R3''
 
Jump And Link con instruzioni di tipo '''I'''
 
'''JALR R3''' Salva l'indirizzo della prossima istruzione nel registro R31 e assegna al program counter il valore di R3, ovvero ''R31 <- (PC + 4) ; PC <- R3''
 
Jump con istruzione di tipo '''J'''
Le istruzioni di tipo J usano un solo operatore di offset da 26 bit
 
'''J alfa''' Salta all'operazione nell'indirizzo di memoria distante ''alfa'' dalla prossima istruzione da eseguire, ''PC <- (PC + 4) + alfa''
 
Jump And Link con istruzione di tipo '''J'''
 
'''JAL alfa''' Si comporta come l'operazione di Jump, ma salvando l'indirizzo della prossima istruzione del registro R31, ''R31 <- (PC + 4); PC <- (PC + 4) + alfa''
 
 
L'operazione di ritorno da una procedura è
'''JR R31''' Assegna al program counter il valore che un'istruzione di Jump And Link ha memorizzato in R31, ''PC <- R31''
 
 
 
Le operazioni di ''Branch'' invece possono essere soltanto di tipo I,
l'OP code definisce il tipo di controllo da eseguire prima di effettuare il salto, il primo operatore è il registro su cui fare questo controllo, il secondo è l'offset di 16 bit relativo, come nei casi precedenti, ad program counter, per cui
 
'''BNEZ R3, alfa''' Salta all'istruzione nell'indirizzo di memoria (PC + 4) + l'offset ''alfa'' soltanto se R3 non è uguale a zero (Branch Not Equal to Zero), in altri termini ''if (R3 != 0) : PC <- (PC + 4) + alfa''
 
 
=== Esempi di codice in assembler per una macchina R-R ===
 
==== Somma degli elementi di un vettore A di 8 elementi ====
 
Se si utilizzano i seguenti registri per
* R1 per la somma corrente
* R2 per l’indice
* R3 per il contatore delle iterazioni
 
Il codice si può scrivere
 
ADD R1, R0, R0 ; azzera la somma corrente
ADD R2, R0, R0 ; R2 vale indice * 4 (4 e’ numero byte per word)
ADDI R3, R0, 8 ; inizializza il contatore
CICLO LW R4, A(R2) ; indirizzo dell’operando in memoria calcolato a run time
ADD R1, R1, R4 ; aggiorna somma corrente
ADDI R2, R2, 4
ADDI R3, R3, -1
BNEZ R3, CICLO
SW Z(R0), R1 ; Z e’ la variabile con il risultato
 
Ovviamente si può sostituire ADDI R3,R3,-1 con SUBI R3,R3,1
 
==== Creazione di uno Stack pointer che supporti l'annidamento ====
 
Abbiamo visto che il DLX non supporta l'annidamento ( ''nesting'' ) delle chiamate a procedura poichè non ha un stack ed utilizza come unico registro di rotorno R31, con questo codice si può implementare via software uno stack pointer con il registro R30.
R30 rappresenta l'indirizzo dell'ultima word di 32 bit (4 byte) inserita nello stack, per cui decresce man mano che lo stack si riempie e cresce man mano che si svuota.
 
Le prime due istruzioni vanno aggiunte prima delle istruzioni della procedura e servono per memorizzare il valore di R31 (indirizzo di ritorno dalla procedura) nello stack implementato grazie al registro R30
 
SUBI R30, R30, 4 ; Decrementa lo stack
SW 0(R30), R31 ; Salva nell'indirizzo di memoria il valore di R31
 
... istruzioni della procedura ...
 
Al termine della procedura sono necessarie alcune righe aggiuntive per permettere il ritorno all'istruzione mappata nell'ultimo indirizzo di memoria dello stack pointer
 
LW R31, 0(R30) ; Preleva l'indirizzo a cui fare ritorno
ADDI R30, R30, 4 ; Incrementa lo stack visto che sto uscendo dalla procedura
JR R31
 
==== Gestione di word (32 bit) ====
 
Supponiamo di voler copiare un vettore di 1K word (32 bit) memorizzato
all’indirizzo 1000H in un vettore di identico formato memorizzato all’indirizzo 40001000H.
Le istruzioni del DLX permettono operandi (immediati) al massimo di 16 bit per cui si usino le istruzioni
 
LHI Rx, IMMEDIATE (Load High Immediate), che carica nella parte alta (16 bit più significativi) del registro destinazione Rx una costante a 16 bit ed azzera la parte bassa (16 bit meno significativi) di tale registro
 
ORI Rx, IMMEDIATE (OR Immediate) scrive in Rx il valore IMMEDIATE senza estenderlo (corrispondere a scrivere soltanto i 16 bit meno significativi)
 
 
ADDI R1,R0,1000H ; Inizializza R1 per leggere il vettore dal suo ultimo elemento ( 1000H - 4 )
LHI R2,4000H ; Costruisci R2 come l'indirizzo di memoria su cui salvare il vettore
ORI R2,R2,2000H ; che andrà da 40001000H a (40002000H -4 )essendo da 1K word
LOOP: SUBI R1,R1,4 ; decrementa R1
LW R3,1000H(R1) ; carica la parola all'ultimo indirizzo del vettore
SUBI R2,R2,4 ; decrementa anche R2
SW 0(R2),R3 ; Memorizza la parola nell'indirizzo di R2
BNEZ R1,LOOP ; Dopo 1K word il valore di R1 è decrementato fino ad arrivare a 0
 
 
==== Massimo di un vettore di unsigned-word (32 bit) ====
 
Si scriva una procedura che ricevendo in ingresso i parametri
R1 : registro con l'indirizzo del vettore
R2 : dimensione del registro (numero di elementi)
R3 : registro usato dalla procedura per memorizzare il valore massime del vettore di unsigned-word
 
 
PROC: ADD R3,R0,R0 ; inizializzazione del massimo corrente, per ora a 0
LOOP: LW R4,0(R1) ; lettura dalla memoria dell’i-esima word
SGTU R5,R4,R3 ; confronto con il massimo corrente
BEQZ R5,SKIP ; “skip” se word corrente è minore del massimo corrente
ADD R3,R4,R0 ; aggiornamento del massimo corrente ( solo se BEQZ non effettua il salto di "skip")
SKIP: ADDI R1,R1,4 ; incremento del puntatore alla word successiva ( 1 word = 4 byte )
SUBI R2,R2,1 ; decremento del contatore delle iterazioni
BNEZ R2,LOOP ; verifica che ci siano altri elementi da processare
JR R31 ; ritorno al programma chiamante
 
 
 
== Datapath sequenziale ==
 
La funzione fondamentale dell'unità di controllo è la lettura degli operandi dal ''banco dei registri'' (R0 - R31), la loro trasformazione all'interno della ALU e la rimemorizzazione del risultato.
 
Il processore DLX utilizza tre bus: '''S1''', '''S2''', e '''Dest''', i primi due sono di ingresso per la alu e sono pilotati dai registri dell'unità di calcolo, il bus ''Dest'', invece, funziona come uscita per la ALU e ingresso per i registri.
 
Dato che il banco dei registri non viene letto o scritto ad ogni ciclo di clock per velocizzare queste operazioni nell'unità di controllo sono presenti degli elementi di memoria alle due uscite del banco di registri (elementi A e B) ed uno in ingresso ( C )
 
Oltre ai 32 registri già visti vi sono anche i registri:
* '''PC''': ''Program counter'' o contatore di programma
* '''IAR''': registro di indirizzamento delle interruzioni, registro che fa parte dello stato della macchina
* '''MAR''': registro di indirizzamento della memoria,
* '''MDR''': registro di lettura e scrittura della memoria,
* '''IR''': registro delle istruzioni
* '''Temp''': registro temporaneo detto ''a perdere'' perchè è disponibile per la memorizzazione dei dati intermedi durante l'esecuzione di alcune istruzioni del DLX.
 
 
== Fasi di esecuzione delle istruzioni ==
 
Le istruzioni tipiche del DLX viste nei paragrafi precedenti sono tutte eseguite in più cicli di clock, volendo scomporre ogni istruzione in micro-operazioni si osserva come tutte le operazioni del DLX ( ad esclusione di quelle in virgola mobile) siano scomponibili in cinque fasi findamentali:
 
* '''prelievo dell'istruzione'''
* '''decodifica'''
* '''esecuzione ed indirizzamento'''
* '''accesso alla memoria'''
* '''scrittura del risultato'''
 
=== Prelievo dell'istruzione ===
 
Il Datapath, a partire dal valore del PC, preleva l'istruzione da eseguire dalla memoria scrivendola nel registro delle istruzioni:
 
MAR <- PC;
IR <- M[MAR]
 
Il contenuto del PC deve essere trasferito nel MAR visto che soltanto quest'ultimo è collegato alla memoria, mentre il PC non lo è
 
=== Decodifica ===
 
Viene decodificata l'istruzione e contemporaneamente si accede al banco dei registri per leggerli, i valori dei registri saranno salvati nei due elementi di memoria, viene inoltre incrementato il PC
 
A <- Rs1;
B <- Rs2;
PC <- PC + 4
 
La decodifica dell'operazione può avvenire in parallelo con la lettura dai registri soltanto perchè la posizione dei ''registri sorgente'' all'interno di una istruzione DLX è fissa dopo i 6 bit del codice operativo, si parla di ''decodifica a campi fissi''. Per lo stesso motivo anche l'estensione in segno del valore immediato viene effettuata in questa fase qualora l'istruzione contenga un valore immediato
 
=== Esecuzione e indirizzamento ===
 
L'ALU elabora gli operandi decodificati nella fase precedente eseguendo una delle possibili funzioni:
 
==== Riferimento alla memoria ====
 
L'ALU calcola l'indirizzo effettivo della cella di memoria e viene inizializzato il registro MDR per un'operazione di memorizzazione
 
MAR <- A + (IR16)16##IR16..31
MDR <- Rd
 
==== Funzione dell'ALU ====
 
L'ALU esegue l'operazione specificata dal codice operativo con operandi Rs1 (A) e Rs2 (B) oppure con il valore immediato esteso di segno
 
C <- A op ( B or (IR16)16##IR16..31 )
 
 
==== Diramazione o salto ====
 
L'ALU calcola l'indirizzo del salto sommando al PC il valore immediato esteso in segno, viene anche controllato un registro per decidere se l'indirizzo va caricato o meno nel PC ( è il caso delle diramazioni )
 
C <- PC + (IR16)16##IR16..31;
cond <- (A op 0)
 
op è l'operatore di relazione specificato dal codice operativo ( BEQZ -> op = EQual ovvero "==" )
Le istruzioni ''JAL'' e ''TRAP'' sono simili a qualla descritta ma in più memorizzano l'indirizzo di ritorno rispettivamente in R31 e ''IAR''.
 
 
=== Accesso alla memoria ===
 
Questa fase è attraversata soltanto dalle istruzioni di caricamento, memorizzazione, salti e diramazioni.
 
==== Caricamento ====
 
Se richiesto l'accesso in memoria il datapath preleva i dati all'indirizzo calcolato in precedenza
 
MDR <- M[MAR]
 
==== Memorizzazione ====
 
Se l'istruzione è una memorizzazione i dati vengono scritti
 
M[MAR] <- MDR
 
==== Diramazione / Salto ====
 
Se si verifica la condizione calcolata nella fase precedente il contenuto del PC viene sostituito con l'indirizzo di destinazione della diramazione
 
if (cond) PC <- C
 
Per i salti incondizionati cond è sempre vera
 
 
=== Scrittura del risultato ===
 
Il risultato in uscita alla ALU viene scritto nel banco dei registri, sia che provenga dalla memoria
 
Rd <- MDR
 
che dalla ALU
 
Rd <- C
 
== Unità di controllo per il DLX ==
 
L'unità di controllo è la parte della CPU che si occupa di indicare all'unità di calcolo le micro-operazioni da eseguire ad ogni ciclo di clock durante l'esecuzione delle istruzioni.
 
[[Immagine:Diagramma_stati_controller_parte_comune.png|left]]
 
 
 
Solitamente si usa un diagramma a stati finiti per specificare le attività svolte dall'unità di calcolo, in questo diagramma ogni stato corrisponde ad un ciclo di clock e le operazioni eseguite sono inserite all'interno dello stato.
 
 
 
 
La prima fase di esecuzione di una istruzione è distribuita nei primi tre stati del diagramma.
 
La seconda fase viene eseguita nel terzo stato
 
 
 
* durante il primo ciclo di clock il valore del PC viene caricato nel registro di indirizzamento della memoria,
MAR <- PC
* nel secondo stato il registro delle istruzioni viene caricato dalla memoria
IR <- M[MAR]
* nel terzo stato viene incrementato il PC e vengono, durante lo stesso ciclo di clock, caricati i due operandi dei registri Rs1 e Rs2 in A e B
PC <- PC + 4
A <- Rs1
B <- Rs2
 
Lo stato successivo del diagramma differisce a seconda che si stia facendo un trasferimento dati, un'operazione ALU, una inizializzazione un salto o diramazione
 
Si possono anche rappresentare i primi due stati con uno solo
IR <- M[PC]
ricordando che in condizioni ideali l'operazione richiede due cicli di clock.
 
I clock necessari per eseguire l'operazione di caricamento del registro IR dipendo fortemente dai tempi di accesso alla memoria, che possono richiedere più di un clock, per cui si indica il primo stato come stabile su se stesso fintanto che l'accesso alla memoria è incompleto
 
 
=== Controller per le istruzioni di memoria ===
 
Sia per le istruzioni di caricamento che memorizzazione il primo stato (dopo i tre comuni a tutti i controller visti prima) è
 
MAR <- A + (IR16)16 ## IR16..31
 
viene quindi calcolato il registro di indirizzamento alla memoria, la fase successiva differisce a seconda che si tratti di
 
==== controller Caricamento ====
 
MDR <- M[MAR]
 
Viene prelevata l'informazione dalla memoria e
 
C <- MDR
 
inserita nell'elemento di memoria C,
quest'ultima operazione si riferisce al casi di Load Word, infatti il MDR conterrà 32 bit, se l'operazione di caricamento opera con Half Word, Byte o dati diversi si selezionano dal MDR soltanto i bit richiesti
 
'''LB''' C <- (MDR24)24 ## MDR24..31
'''LBU''' C <- (0)24 ## MDR24..31
'''LH''' C <- (MDR16)16 ## MDR16..31
'''LHU''' C <- (0)16 ## MDR16..31
 
Al successivo clock il valore dell'elemento di memoria riempie il registro di destinazione
 
Rd <- C
 
Dopo questa micro-operazione il diagramma degli stati ritorno all'inizio con il prelevamento di una istruzione dalla memoria
 
==== controller Memorizzazione ====
 
MDR <- B
 
Il dato da memorizzare viene inserito nell'apposito registro
 
M[MAR] <- MDR
 
e scritto in memoria all'indirizzo precedentemente calcolato, questo stato è stabile fino a che l'accesso in memoria non sia completo, può quindi richiedere più clock
 
=== Controller per le istruzioni ALU ===
 
Nelle istruzioni ALU l'operazione indicata dall'op code viene eseguita tra l'elemento di memoria A e il registro ''Temp'', per cui il primo blocco consiste nel riempire questo registro con il secondo elemento di memoria ( B ) se si tratta di una operazione tra registri, andrà invece riempito con l'immediato (esteso di segno a 32 bit) se l'operazione da eseguire è di tipo I
 
''Registro'' Temp <- B
''Immediato'' Temp <- IR0..15##(IR15)16
''Immediato senza segno'' Temp <- IR0..15 ## (0)16
 
Dopo di che viene eseguita la micro-operazione corrispondente al codice operativo specificato
 
'''ADD''' C <- A + Temp
'''AND''' C <- A & Temp
'''SUB''' C <- A - Temp
'''OR''' C <- A | Temp
 
Ed il risultato viene scritto sul registro destinazione
 
Rd <- C
 
=== Controller per l'istruzione di salto ===
 
==== JR ====
 
PC <- A
 
Essendo un'istruzione di ritorno deve solo aggiornare il PC
 
==== J ====
 
PC <- PC + IR0..25 ## (IR25)6
 
Modifica il program counter con l'immediato
 
==== JAL ====
 
C <- PC
PC <- PC + IR0..25 ## (IR25)6
R31 <- C
 
Prima di sovrascrivere il PC ne salva il valore in R31
 
==== JALR ====
 
C <- PC
PC <- A
R31 <- C
 
=== Controller per l'istruzione di inizializzazione ===
 
O anche istruzioni di set
 
La prima micro-operazione è la copia nel registro ''Temp'' del valore del secondo operando
 
'''Registro''' Temp <- B
'''Immediato''' Temp <- IR0..15 ## (IR15)16
 
Al successivo ciclo di clock viene eseguita l'operazione di confronto, per cui
 
'''SEQ''' A == Temp
'''SNEQ''' A != Temp
'''SLT''' A < Temp
'''SGE''' A >= Temp
'''SGT''' A > Temp
'''SLE''' A <= Temp
 
Nel diagramma degli stati a questo punto si ha una diramazione a seconda del risultato del precedente confronto
 
'''vero''' C <- 1
'''falso''' C <- 0
 
L'ultima operazione è quella di scrivere il contenuto di C nel registro destinazione
 
Rd <- C
 
=== Controller per l'istruzione di branch ===
 
I branch sono di due tipi
 
'''BEQZ''' A == 0
'''BNEZ''' A != 0
 
E la diramazione che ne segue comporta il ritorno all'inizio del datapath in caso di esito negativo,
oppure di aggiornamento del PC in caso di esito positivo
 
PC <- PC + IR0..15 ## (IR15)16
 
=== Altri esempi di controller ===
 
Supponiamo di voler creare una nuova istruzione DLX del tipo
CMPS Rx, Ry, Rz
 
L'istruzione confronta il dato di tipo '''unsigned byte''' situato in memoria all'indirizzo Rx con quello all'indirizzo Ry e pone Rz a 1 se i due dati sono uguali
 
Una descrizione RTL delle operazioni svolte dalla nuova istruzione è data da:
if (M[Rx] = M[Ry])
then Rz <- 1
else Rz <- 0;
 
Il controller (datapath) di questa nuova istruzione, compresi gli stati di fetch e decodifica, sarà
 
IR <- M[PC] ; fetch dell'istruzione
 
A <- Rx
B <- Ry ; decodifica
PC <- PC + 4
 
MAR <- A ; preparo il registro di accesso alla memoria
 
MDR <- M[MAR] ; ed accedo
 
Temp <- MDR0..7 ## (0)24 ; Salvo soltanto i primi 8 bit (''unsigned byte'') nel registro temporaneo
 
MAR <- B ; e mi preparo a prelevare anche B
 
MDR <- M[MAR] ; lo prelevo
 
Temp == MDR0..7 ## (0)24 ; e lo confronto con temp
 
'''uguali''' C <- 1 ; se i dati prelevati dalla memoria sono uguali
'''diversi''' C <- 0 ; oppure diversi, l'unità di calcolo eseguirà solo una delle due istruzioni
 
Rz <- C ; scrivo il valore calcolato nel registro destinazione
 
Ogni blocco rappresenta un ciclo di clock, ad esclusione di quelli che accedono alla memoria per i quali la durata dipende dal tempo di accesso alla memoria
 
=== Numero di clock necessari per eseguire le istruzioni ===
 
Il numero di cicli di clock per eseguire una qualsiasi istruzione del DLX si ottiene contando gli stati del controller per ogni istruzione, il valore reale dipende poi dal numero e dalla durata degli accessi alla memoria, la seguente tabella riassume il numero di cicli di clock e il numero degli accessi alla memoria per le istruzioni del DLX nel caso in cui l'accesso alla memoria duri '''1''' clock
 
 
{| cellpadding='8'
!'''Istruzione'''
!'''Numero minimo
di clock'''
!'''Accessi alla
memoria'''
|-
|Caricamento
|<center> 6 </center>
|<center> 2 </center>
|-
|Memorizzazione
|<center> 5 </center>
|<center> 2 </center>
|-
| ALU
|<center> 5 </center>
|<center> 1 </center>
|-
| Inizializzazione
|<center> 6 </center>
|<center> 1 </center>
|-
| Jump
|<center> 3 </center>
|<center> 1 </center>
|-
| Jump and link
|<center> 5 </center>
|<center> 1 </center>
|-
| Branch taken
(eseguito)
|<center> 4 </center>
|<center> 1 </center>
|-
| Branch not taken
|<center> 3 </center>
|<center> 1 </center>
|}
Qualora l'accesso alla memoria duri più cicli di clock, questi andrebbero moltiplicati al numero di accessi alla memoria e sommati al numero minimo di cicli di clock, in questo caso si parla di ''cicli di stallo della memoria'' o ''stati di attesa'' o '''wait'''. In macchine dotate di cache i cicli di wait valgono zero ( l'accesso alla memoria cache richiede quindi un solo clock )
 
==== Calcolo del CPI ====
 
Supponiamo di avere un programma che esegue le seguenti operazioni con frequenza relativa:
 
Caricamento 21 %
Memorizzazione 12 %
ALU 37 %
Inizializzazione 6 %
Jump 2 %
Jump an link 0 %
Branch taken 12 %
Branch not taken 11 %
 
Il CPI medio, ricordando la formula data nel primo capitolo, si ottiene sommando i prodotti tra il CPI di ogni istruzione (secondo la tabella sopra riportata e gli stati di wait) e la sua frequenza relativa,
in questo esempio il CPI medio risulta '''6,28'''
 
 
== DLX in Pipeline ==
 
Nei paragrafi precedenti abbiamo visto come le istruzioni DLX siano tutti divisibili in cinque blocchi funzionali:
 
* '''IF''': prelievo dell'istruzione,
* '''ID''': decodifica dell'istruzione,
* '''EX''': esecuzione e calcolo dell'indirizzo,
* '''MEM''': accesso alla memoria,
* '''WB''': scrittura del risultato.
 
Assegnando le cinque operazioni a blocchi distinti, è possibile svolgere più istruzioni contemporneamente:
 
Clock 1 2 3 4 5 6 7 8 9
''Istruzione i'' IF ID EX MEM WB
''Istruzione i+1'' IF ID EX MEM WB
''Istruzione i+2'' IF ID EX MEM WB
''Istruzione i+3'' IF ID EX MEM WB
''Istruzione i+4'' IF ID EX MEM WB
 
questo comporta, con pipeline piena, la scrittura del risultato di un'operazione ad ogni ciclo di clock, ovvero un CPI ideale di '''1''', nonostante il tempo per eseguire un'operazione ( ''latenza'' ) resti invariato .
 
Il CPI reale della pipeline dipende da più fattori, primo fra tutti l'effettiva durata del clock, se tra gli stadi di pipeline bisogna aggiungere degli elementi di memoria il tempo di un ciclo di clock aumenta di un fattore pari alla somma tra il ''setup time'' (tempo per memorizzare il valore) e il ''delay time'' (ritardo di propagazione).
Ben più influente è la necessità di bilanciare la pipeline, se infatti lo stadio di MEM richiede più cicli di clock, è necessario che tutti gli stadi della pipeline abbiano la durata massima per consentirne l'esecuzione.
 
Sono quindi determinanti gli elementi di memoria, per questo motivo si utilizzano le memorie cache, ovvero memorie entegrate nella CPU con tempi di accesso della durata di un clock.
 
Ovviamente la pipeline è possibile soltanto se la sovrapposizione delle istruzioni non sovraccarichi le risorse, è ad esempio impossibile richiedere alla ALU di eseguire un'operazione qualsiasi mentre un'altra istruzione vuole calcolare un indirizzo effettivo (incremento del program counter), vanno quindi apportate alcune modifiche al normale funzionamento della CPU sequenziale:
 
# Il PC deve essere incrementato durante la fase di IF con un incrementatore addizionale per lasciare libera la ALU allo stadio di EX
# Ad ogni ciclo di clock deve essere prelevata una nuova istruzione (stadio IF)
# Sono necessari due MDR (Registro di accesso alla memoria) per gestire il caso di una LOAD seguita da una STORE, i due registri verranno indicati con '''MDR''' e '''SMDR''' (Store MDR)
# In ogni ciclo di clock vanno eseguiti due accessi alla memoria, IF e MEM, per cui sono necessarie due cache separate per dati ed istruzioni
# Occorrono elementi di memoria per conservare i valori da usare negli stadi successivi della pipeline, ma che possono essere modificati dall'istruzione successiva, ovvero l'istruzione, il risultato ALU il prossimo valore del PC.
 
Nonostante questi accorgimenti, una pipeline così costruita funzionerebbe bene se le istruzioni fossero tutte tra di loro indipendenti, qualora non lo fossero si verificherebbero delle '''alee''' o '''conflitti''' (in inglese ''Hazard''), che possono essere di tre tipi:
 
* ''Conflitti strutturali'' che derivano da problemi nell'uso delle risorse
* ''Conflitti tra dati'' che si verificano quando un'istruzione dipende dai risultati dell'istruzione precedente
* ''Conflitti di controllo'' che sono legati alle diramazioni e alle istruzioni che modificano il PC
 
Un'alea nella pipeline rende necessario l'arresto della stessa, ovvero uno '''stallo''.
Uno stallo richede che alcune istruzioni possano proseguire per consentire di risolvere il conflitto, mentre altre debbano essere sopsese, questo ovviamente comporta un peggioramento delle prestazioni, ovvero il CPI di un'istruzione sarà il CPI ideale ( 1 ) più il numero di stalli necessari per risolvere il conflitto.
 
 
=== Conflitti strutturali ===
 
Dato che l'esecuzione delle istruzioni non è più lineare, ma sovrapposta, per rendere possibili le diverse combinazioni di istruzioni da eseguire contemporaneamente sono necessari dei collegamenti tra i diversi stadi della pipeline. Se mancano alcuni collegamenti, ovvero alcune combinazioni non sono possibili, si dice che la macchina presenta ''conflitti strutturali''.
 
Per esempio una macchina potrebbe disporre di un'unica memoria per dati ed istruzioni, ciò comporta uno stallo ogni volta che viene eseguita un'istruzione con riferimento ai dati in memoria perchè la macchina non può eseguire l'operazione di IF che comporta la lettura in memoria dell'istruzione da eseguire
 
''Istruzione di caricamento'' IF ID EX MEM WB
''Istruzione i+1'' IF ID EX MEM WB
''Istruzione i+2'' IF ID EX MEM WB
''Istruzione i+3'' '''stallo''' IF ID EX MEM WB
 
L'istruzione i+3 non può iniziare finchè l'istruzione di caricamento non libera l'unica porta di memoria disponibile.
 
I conflitti strutturali comportano un CPI inferiore e sono sempre risolvibili aumentando la complessità, e quindi i costi, della macchina. La realizzazione di una pipeline completa (senza conflitti strutturali) comporta, inoltre, un aumento dei tempi di latenza delle operazioni che in alcuni casi si può trasformare in un peggioramento delle prestazioni
 
 
=== Conflitti tra dati ===
 
I conflitti tra dati si verificano quando vi è una dipendenza tra le istruzioni inserite nella pipeline.
 
Il seguente spezzone di codice presenta una dipendenza:
ADD R1, R2, R3
SUB R4, R1, R5
Eseguendo le operazioni in pipeline
Cicli di clock 1 2 3 4 5 6
Istruzione ADD IF ID EX MEM WB
Istruzione SUB IF ID EX MEM WB
Il risultato della prima istruzione è disponibile sul banco dei registri dopo lo stadio WB e quindi al clock 5, mentre la seconda operazione legge il valore in R1 nello stadio ID, al clock 3, in anticipo.
 
Tuttavia l'istruzione ADD calcola il valore di R1 già alla fine dello stadio di EX, una soluzione per questo tipo di conflitti è una ''unità di anticipazione'' o '''forwarding unit''' ('''FU''').
La FU consiste nel retroazionare il risultato dell'ALU agli elementi di memoria posti in ingresso all'ALU.
Se la FU rileva che l'operazione ALU in corso richiede come ingresso l'uscita dell'operazione immediatamente precedente, la logica di controllo seleziona come ingresso il valore retroazionato invece del valore letto nel banco dei registri.
 
Occorre, inoltre, passare i risultati non solo all'istruzione che segue immediatamente, ma anche alle successive, ad esempio
ADD R1, R2, R3 IF ID EX MEM WB
SUB R4, R1, R5 IF '''ID''' EX MEM WB
AND R6, R1, R7 IF '''ID''' EX MEM WB
OR R8, R1, R9 IF '''ID''' EX MEM WB
XOR R10,R1, R11 IF ID EX MEM WB
In grassetto sono indicati tutti gli stadi per i quali è richiesta una forwarding unit, l'XOR non ne ha bisogno dato che al momento dell'ID il valore di R1 è già stato scritto dalla prima istruzione.
Si può ridurre il numero di istruzioni da anticipare considerando che il canco dei registri può essere usato due volte in un solo ciclo di clock. Eseguendo le letture nella seconda metà di ID e le scritture nella prima metà di WB l'istruzione OR non necessita di FU.
 
Ridurre il livello di cortocircuitazione richiede un elemento di memoria e una rete logica che decide se l'istruzione condivide il registro sorgente o destinazione con una adiacente.
 
Per le operazioni ALU, usando le forwarding unit, si riescono ad eliminare tutti i conflitti tra dati.
 
Il concetto di anticipazione può essere anche esteso a unità funzionali diverse, ad esempio
ADD R1, R2, R3
SW 25(R1), R1
richiede che l'uscita dell'ALU sia anticipata di nuovo sull'ALU per poter essere calcolato l'indirizzo effettivo, ma anche sull'MDR per poter essere memorizzato senza stalli.
 
I possibili conflitti di dati tra un'istruzione ''i'' e la successiva ''j'' sono:
* '''RAW''' (''Read After Write, lettura dopo scrittura'') l'istruzione ''j'' tenta di leggere una sorgente prima ancora che ''i'' l'abbia scritta.
* '''WAR''' (''Write After Read, scrittura dopo lettura'') ''j'' tenta di scrivere una destinazione prima che sia stata letta da ''i''. Questo tipo di conflitto non è possibile nel DLX dato che per qualsiasi operazione la lettura precede (in ID) la scrittura (in WB). Questo conflitto si verificherebbe con istruzioni che scrivono risultati nei primi stadi della pipeline.
* '''WAW''' (''Write After Write, scrittura dopo scrittura'') ''j'' tenta di scrivere un operando prima che sia stato scritto da ''i'', il risultato scritto corrisponderebbe erroneamente a quello di ''i''. Anche questo conflitto non è possibile nel DLX.
 
Il caso RAR non è un conflitto.
Il primo esempio considerato in questo paragrafo è un conflitto RAW.
 
 
Non tutti i conflitti sui dati si possono risolvere senza conseguenze sulle prestazioni, ad esempio
LW R1, 20(R2) IF ID EX '''MEM''' WB
ADD R3, R1, R4 IF ID '''EX''' MEM WB
Il valore della prima istruzione sarà disponibile soltanto dopo lo stadio di MEM, mentre l'istruzione ALU successiva ha bisogno di quel valore nello stadio EX, ovvero all'inizio dello stesso ciclo di clock.
In questo caso non è possibile realizzare un'unità anticipatrice, ma il conflitto tra i dati può essere risolto soltanto con uno stallo
LW R1, 20(R2) IF ID EX '''MEM''' WB
ADD R3, R1, R4 IF ID '''stallo''' '''EX''' MEM WB
 
In questo caso si è rappresentato lo stallo dopo la fase EX, in realtà, per quanto riguarda la pipeline del DLX, tutti i conflitti possono essere rilevati già durante la fase di ID, un'istruzione si dice ''avviata'' quando passa dallo stadio di decodifica (ID) a quello di esecuzione (EX); se esiste un conflitto tra dati l'istruzione viene sospesa prima di essere avviata.
 
Esiste anche una tecnica, detta di '''scheduling''' della pipeline, che permette di ridurre il numero di stalli riordinando il codice per eliminare i conflitti. Questo compito è affidato al compilatore.
 
Se ad esempio se si vogliono eseguire le operazioni
a = b + c
d = e - f
con a,b,c,d,e,f word in memoria, il codice è
LW R1, b
LW R2, c
ADD R3, R1, R2
SW a, R3
LW R4, e
LW R5, f
SUB R6, R4, R5
SW d, R6
questo codice presenta due situazioni di stallo per conflitto tra dati ( ADD e SUB dopo un LW ), riorganizzando il codice in questo modo
LW R1, b
LW R2, c
LW R4, e ;scambio ADD con la LW successiva in modo da evitare lo stallo
ADD R3, R1, R2 ;questa ADD non dipende dall'istruzione immediatamente precedente
LW R5, f
SW a, R3 ;ritardo la SW per risolvere il secondo conflitto tra dati
SUB R6, R4, R5
SW d, R6
 
Il codice riscritto non presenta stalli.
 
La tecnica della schedulazione può essere impossibile da realizzare, in questo caso viene inserita una operazione nulla ('''NOP''', ''No-OPeration'') tra le due in conflitto. Questa modifica, per quanto riguarda il CPI, è del tutto equivalente ad uno stallo, ma permette di gestire soltanto tramite la schedulazione i conflitti tra dati, semplificando la realizzazione della CPU
 
=== Conflitti di controllo ===
 
= 8086 =
 
L'Intel 8086 nasce come un'estensione del 8080, una macchina ad accumulatore. E' quindi sostanzialmente un ibrido, perché pur avendo 14 registri, nessuno di questi è ad uso generale in quanto ognuno ha un uso ben specifico.
 
L'architettura è a 16 bit così come i registri, ma per ottenere un'indirizzabilità maggiore i progettisti hanno aggiunto dei ''segmenti'' che estendono lo spazio di indirizzamento a 20 bit.
 
Fanno parte della stessa famiglia, sono quindi molto simili, i processori 80186, 80286 (indirizzamento a 24 bit), 80386 (indirizzamento e parallelismo a 32 bit), 80486 (poche novità rispetto all'80386 ma con prestazioni notevolmente migliorate).
 
 
== Registri e indirizzamento ==
 
Un indirizzo a 20 bit si ottiene da un indirizzo effettivo a 16 bit (''offset'', registro di indirizzo) sommandogli un indirizzo base a 20 bit, con lo scorrimento verso sinistra di quattro posizioni del contenuto di un registro segmento a 16 bit.
 
 
I 14 registri dell'8086 sono raggruppabili in 4 categorie: Dato, Indirizzo, Segmento e Controllo.
 
* '''Dato''': Usati per mantenere dati o per operare su di essi:
** '''AX''' Usato per moltiplicazioni, divisioni, I/O o operando implicito.
** '''BX''' Può essere un registro per contenere l'indirizzo base.
** '''CX''' Usato per le operazioni tra stringhe o istruzioni di ciclo.
** '''DX''' Usato per moltiplicazioni, divisioni e I/O.
* '''Indirizzo''': Usati per formare un indirizzo effettivo di memoria a 16 bit (interno al segmento):
** '''SP''' Puntatore alla sommità dello stack.
** '''BP''' Registro base: usato nell'indirizzamento tramite base.
** '''SI''' Registro indice: usato anche come base per l'indirizzo di una stringa.
** '''DI''' Registro indice: usato anche come base per l'indirizzo di destinazione di una stringa.
* '''Segmento''': Usati per costruire indirizzi di memoria a 20 bit:
** '''CS''' Segmento di codice: è usato per gli accessi alle istruzioni.
** '''SS''' Segmento dello stack: è usato per i riferimenti allo stack.
** '''ES''' Segmento aggiuntivo: usato per le operazioni destinate a una stringa.
** '''DS''' Segmento dati: usato per tutti gli altri casi.
* '''Controllo''': Usati per il controllo dello stato e del programma:
** '''IP''' Instruction Pointer: fornisce l'offset dell'istruzione corrente in esecuzione (16 bit meno significativi del PC).
** '''FLAGS''' Contiene 6 bit relativi alle condizioni (riporto, zero, segno, prestito, parità e overflow) e 3 bit per il controllo dello stato.
 
 
Le istruzioni ALU sono di tipo Memory-Register (2,1) e consentono l'uso di un operando immediato.
 
Le modalità di indirizzamento sono
* ''Registro indiretto'': BX, SI, DI.
* ''Base + offset a 8 o 16 bit'': BP, BX, SI, DI.
* ''Indicizzato'': l'indirizzo è la somma di due registri BP+SI, BP+DI, BX+SI, BX+DI.
* ''Indicizzato + offset a 8 o 16 bit'': l'indirizzo è la somma dell'offset con il contenuto di due registri.
 
== Operazioni ==
 
Possono essere suddivise in quattro categorie:
 
* Trasferimento dati, comprendono spostamenti (''move''), inserimento (''push'') ed estrazione (''pop'') di dati dallo stack.
* Istruzioni ALU
* Istruzioni di controllo del flusso, comprendono le diramazioni con e senza condizione, le chiamate e i ritorni dalle chiamate (l'utilizzo dello stack piuttosto che un registro come nel DLX permette il nesting-annidamento delle chiamate a procedura)
* Istruzioni su stringhe, come spostamento e confronto.
 
Alcune delle istruzioni tipiche sono:
* Trasferimento dati
** '''MOV''' Sposta un dato tra due registri o tra un registro e la memoria
** '''PUSH''' Metti un operando nello stack
** '''POP''' Leggi un operando dallo stack e mettilo in un registro
** '''LES''' Carica ES e uno dei GPR dalla memoria
* Istruzioni ALU
** '''ADD''' Addizione tra operando sorgente e operando destinazione
** '''SUB''' Sottrazione dell'operando sorgente da quello di destinazione
** '''CMP''' Confronto tra operando sorgente e destinazione
** '''SHL''','''SHR''' Scorrimento logico rispettivamente a sinistra e a destra
** '''RCR''' Rotazione a destra attraverso il riporto
** '''CBW''' Conversione di un byte in AL in una parola AX
** '''TEST''' And logico tra le configurazione dei flag sorgente e distinazione
** '''INC''','''DEC''' Incremento o decremento dell'operando destinazione
** '''OR''' OR logico
** '''XOR''' OR esclusivo
* Controllo
** '''JNZ''','''JZ''' Salta se è verificata la condizione IP + 8 bit di offset (in alternativa anche JN e JE)
** '''JMP''','''JMPF''' Salto non condizionato
** '''CALL''','''CALLF''' Chiamata a subroutine, l'indirizzo di ritorno è inserito nello stack
** '''RET''','''RETF''' Leggi dallo stack l'indirizzo di ritorno e salta ad esso
** '''LOOP''' Diramazione di ciclio: decrementa CS, se diverso da zero salta a IP + 8 bit di offset.
* Istruzioni su stringhe
** '''MOVS''' Copia la stringa sorgente in quella destinazione
** '''LODS''' Carica un byte o una parola di stringa nel registro A.
Per le istruzioni di stringa esiste un prefisso di ripetizione, questo significa che l'operazione deve essere ripetuta tante volte quanto indicato dal valore nel registro CX.
 
Le istruzioni di controllo di flusso devono essere in gradi di gestire sia trasferimenti ''vicini'' (all'interno del segmento di memoria indirizzabile con i 16 bit del registro) sia trasferimenti ''lontani'' (suffisso F, segmenti che richiedono indirizzi a 20 bit). Nei salti lontani all'OpCode seguono due quantità da 16 bit, una di queste è il puntatore all'istruzione, l'altra è caricata in CS e diventa il nuovo codice di segmento. Un ritorno lontano legge dalla cima dello stack sia il puntatore all'istruzione, sia il codice di segmento.
 
La codifica delle istruzione dell'8086 è piuttosto complessa e presenta diversi formati che possono variare a da uno a sei byte. Inoltre molte delle istruzioni contengono un campo da un bit w che specifica se l'operazione è di un byte o di una parola (2 byte).
Alcune istruzioni contengono insieme con l'OpCode la modalità di indirizzamento e il registro, altre istruzioni ancora usano un byte aggiuntivo, il ''postbyte'', con le informazioni sulla modalità di indirizzamento (è usato spesso per le istruzioni che indirizzano la memoria)
 
 
202

contributi