A p p u n t i d i C a lco la t o ri Elet t ro n ici C a p it o lo 5 T ecn i ch e p er l a real i zzazi on e d ei p roces s ori Introduzione .................................................................................................................. 2 Richiami sulla macchina di von Neumann ........................................................................ 2 Unità di calcolo del processore .................................................................................... 2 Tipica struttura di un processore: il DLX ......................................................................... 3 Fasi fondamentali dell’esecuzione di una istruzione.......................................................... 6 Latenza delle istruzioni ............................................................................................. 12 I L “ CONTROLLO ”.............................................................................................................. 14 Introduzione ................................................................................................................ 14 Diagramma a stati finiti descrittivo del controllo ........................................................... 14 Controllo dedicato (nel DLX) ........................................................................................ 16 Riduzione dei costi dell’hardware del controllo dedicato.............................................. 17 Prestazioni del controllo dedicato............................................................................... 17 Controllo microprogrammato ........................................................................................ 18 Le microistruzioni..................................................................................................... 19 Fondamenti della microprogrammazione ..................................................................... 20 Esecuzione di una istruzione................................................................................... 21 Esempio: microprogramma per la fase di fetch di una istruzione................................ 23 Riduzione dei costi dell’hardware .............................................................................. 24 Codifica delle linee di controllo .............................................................................. 24 Formati multipli di microistruzioni ......................................................................... 25 Aggiunta di controllo dedicato per la condivisione di microcodice............................ 26 Riduzione del CPI ..................................................................................................... 27 Uso di microcodice specializzato ............................................................................ 27 Aggiunta di controllo dedicato ................................................................................ 27 Uso del “parallelismo” ........................................................................................... 28 Questione degli accessi incompleti alla memoria ............................................................ 28 ARCHITETTURA AD ACCUMULATORE .................................................................................. 29 Descrizione dell’architettura ........................................................................................ 29 Modalità di indirizzamento e loro implementazione ........................................................ 30 Istruzioni di salto ......................................................................................................... 33 Caso particolare: salto da interruzione ........................................................................ 35 L E INTERRUZIONI ............................................................................................................. 37 Introduzione ................................................................................................................ 37 Terminologia delle interruzioni .................................................................................. 38 Concetti generali sulle interruzioni ............................................................................... 38 Classificazione degli eventi che determinano interruzioni ............................................... 39 Rilevazione delle interruzioni da parte del controllo ....................................................... 41 Il controllo per il DLX .................................................................................................. 44 Appunti di “Calcolatori Elettronici” – Capitolo 5 Introduzione Il termine “architettura”, in campo edilizio, definisce sostanzialmente quale forma debba avere un edificio; ad essa si affianca la “carpenteria”, che invece determina la qualità della costruzione. L’equivalente della carpenteria, nell’ambito dei calcolatori elettronici, è la realizzazione, il cui ruolo è quello di stabilire due fondamentali parametri per le prestazioni: il numero di cicli di clock per istruzione (brevemente CPI) e la durata del singolo ciclo di clock (detta anche periodo di clock). In questo capitolo vogliamo esporre i concetti fondamentali della progettazione dei processori, concentrandoci in particolar modo sul controllo di flusso e sulle interruzioni. Richiami sulla macchina di von Neumann Parecchi anni fa, il generico calcolatore elettronico fu suddiviso da von Neumann in un certo numero di componenti di base, che esistono ancora oggi, nonostante molte cose siano cambiate da allora: Unità Centrale di Elaborazione (CPU o processore) Unità di controllo Unità di calcolo registri Memoria centrale Stampante Disco BUS Organizzazione di un semplice calcolatore con una CPU e due dispositivi di I/O La CPU, che al giorno d’oggi è diventato sinonimo di processore, è il nucleo del calcolatore: essa contiene tutto tranne la memoria e le funzioni di ingresso (Input) e di uscita (Output). A sua volta, il processore comprende fondamentalmente due elementi: unità di controllo e unità di calcolo. U Un niittàà d dii ccaallccoolloo d deell p prroocceessssoorree Oggi, l’organo “aritmetico” definito da von Neumann prende il nome di unità di calcolo ed è costituito da tre “tipi” di elementi: • due o più unità di esecuzione, come l’unità aritmetico-logica (ALU) e l’unità di scorrimento; • registri; Autore: Sandro Petrizzelli 2 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori • collegamenti tra i registri. Dal punto di vista del programmatore, l’unità di calcolo contiene la maggior parte dello stato del processore: tale “stato” corrisponde alle informazioni che devono essere salvate qualora si voglia sospendere e poi riattivare l’esecuzione di un programma. Oltre ai registri di tipo generale, che sono visibili all’utente, l’unità di calcolo comprende almeno altri tre particolari registri, le cui funzioni saranno descritte più avanti: • il registro contatore di programma (PC, program counter); • il registro di indirizzo delle interruzioni (IAR, Interrupt Address Register); • il registro di stato (detto talvolta PSW, cioè Program Status Word) In particolare, il registro di stato contiene tutti gli indicatori di stato (flag) della macchina: ad esempio, ci sono l’indicatore di abilitazione delle interruzioni, gli indicatori di condizione e così via. Dato che la tecnologia adottata determina la realizzazione del calcolatore, questa a sua volta determina la durata del ciclo di clock (periodo di clock). Tale durata è chiaramente determinata dai circuiti più lenti: generalmente, è proprio l’unità di calcolo ad essere più lenta degli altri circuiti del processore. La stessa l’unità di calcolo determina in modo preponderante il costo del processore, visto che solitamente comprende la metà del transistor del processore e occupa praticamente la metà della sua superficie di silicio. Nonostante l’importanza rivestita dall’unità di calcolo, essa risulta comunque il “componente” più semplice da progettare. Tipica struttura di un processore: il DLX E’ opportuno esaminare una schematizzazione della struttura del processore che useremo in seguito per i nostri discorsi, riportata nella prossima figura (pag. 4). La prima cosa che si nota nella figura è la suddivisione del processore in unità di controllo (a sinistra) ed unità di calcolo; quest’ultima è a sua volta suddivisa in una serie di componenti che descriveremo tra un attimo e costituisce la parte preponderante del processore. Nella parte inferiore della figura viene anche schematizzato il collegamento del processore con la memoria, tramite tre “linee”: due linee (collegate ai registri IR ed MDR) servono per il trasferimento dei dati (sia in ingresso sia in uscita) e delle istruzione (solo in uscita) in memoria, mentre la terza linea (collegata al registro MAR) serve al processore per specificare l’indirizzo di memoria coinvolto nelle operazioni di trasferimento. Notiamo inoltre che i collegamenti dell’unità di controllo sono tratteggiati, mentre quelli dell’unità di calcolo sono continui: il motivo è nel fatto che l’unità di controllo, tramite appositi segnali, esercita una sorta di “vigilanza” (o, appunto, “controllo”) su quello che avviene nell’unità di calcolo, la quale invece prevede collegamenti per l’interscambio di segnali corrispondenti a dati veri e propri. aggiornamento: 10 luglio 2001 3 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Processore Unità di calcolo S1 Bus Dest Bus S2 Bus ALU A B Banco dei Registri Unità di controllo C Temp PC IAR MAR IR MDR MUX Ingresso dato Memoria Indirizzo Uscita dato Processore tipico, ripartito in unità di controllo ed unità di calcolo, più la memoria Venendo alla struttura specifica dell’unità di calcolo, possiamo osservare quanto segue: 1 • il processore usa tre distinti bus, denominati S1, S2 e Dest. Il bus Dest ha il compito di prelevare i valori prodotti in uscita dalla ALU e di collocarli o nel banco di registri (passando per l’elemento C) oppure nei registri dedicati o anche ad uno dei due ingressi del multiplexer (MXU); gli altri due bus, invece, portano gli operandi in ingresso alla ALU, prelevandoli dai registri dedicati oppure dal banco dei registri (in questo caso tramite gli elementi A e B); • la funzione fondamentale dell’unità di controllo è la lettura degli operandi dal banco dei registri, la loro “trasformazione” nell’ALU e la rimemorizzazione del risultato; dato che il banco di registri non viene letto e scritto in ogni ciclo di clock, la maggior parte dei progettisti è convinta di dover accelerare queste operazioni, per cui spesso si suddivide la sequenza in cicli di clock multipli e si diminuisce al contempo la durata di ciascun ciclo ( 1). Per questo motivo, nell’unità di calcolo compaiono degli “elementi di memoria” alle due uscite del Questo concetto sarà più chiaro quando parleremo di pipelining Autore: Sandro Petrizzelli 4 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori banco di registri (A e B) ed all’ingresso dello stesso banco (C): si tratta in particolare di latch, che hanno lo scopo di conservare i dati per il tempo necessario affinché la ALU possa svolgere i propri compiti; • il banco di registri contiene i 32 registri di tipo generale (GPR) del DLX; • il registro contatore di programma (PC) ed il registro di indirizzamento delle interruzioni (IAR) fanno parte dello stato della macchina: il primo contiene l’indirizzo della prossima istruzione da eseguire, mentre il secondo viene usato per la gestione delle interruzioni (interrupt), in quanto contiene l’indirizzo di ritorno dalle routine di interrupt ( 2); • sono poi previsti dei registri che invece non fanno parte dello stato della macchina, ma servono per l’esecuzione delle istruzioni: • o il registro di indirizzamento della memoria (MAR) contiene l’indirizzo di memoria cui accedere per la lettura o la scrittura di dati; o il registro di lettura e scrittura della memoria (MDR) contiene i dati da leggere o scrivere in memoria (all’indirizzo contenuto in MAR); o il registro delle istruzioni (IR) contiene sempre la prossima istruzione da eseguire; esso presenta un collegamento diretto con la memoria ( 3), in quanto riceve da essa le istruzioni; si comporta quindi in modo simile al registro MDR, con la differenza che contiene solo istruzioni (mentre l’ MDR contiene solo dati) e viene usato solo per “leggere” dalla memoria (mentre l’ MDR anche per “scrivere”); o il registro temporaneo (Temp) è un “registro a perdere”, nel senso che è disponibile per la memorizzazione dei dati intermedi durante l’esecuzione di alcune istruzioni del DLX; infine, due cose molto importanti da notare sono le seguenti: o in primo luogo, l’unica via di comunicazione tra i bus S1 ed S2 ed il bus Dest passa attraverso la ALU: questo significa, come avremo modo di dire in seguito, che il passaggio di un dato da S1 (o S2) a Dest richiede comunque il “funzionamento” della ALU, la quale naturalmente dovrà lasciar passare inalterato il dato dall’ingresso all’uscita; si dice perciò, in questi casi, che la ALU compie una operazione fittizia, nel senso appunto che il dato in ingresso si ritrova invariato in uscita dopo un certo tempo (necessario alla propagazione dei segnali nei circuiti combinatori che compongono la ALU stessa); o in secondo luogo, si nota la mancanza di un registro Stack Pointer (puntatore allo stack), che molte architetture usano per gestire lo stack (usato, per esempio, per le chiamate a procedure ed i ritorni da procedura): il motivo è che, essendo il DLX una architettura a registri generali, è possibile usare uno qualsiasi dei registri del “banco di registri” per tale funzione. Non solo, ma usando più registri si possono anche realizzare più stack contemporaneamente. 2 Di questo argomento parleremo ampiamente più avanti in questo capitolo. Quando parliamo di “collegamento diretto” tra un qualsiasi registro interno alla CPU e la memoria, ci riferiamo implicitamente ad un collegamento del registro al bus. In particolare, il registro IR, come anche l’MDR, è collegato al bus dati, mentre invece il registro MAR al bus indirizzi, per ovvi motivi. 3 aggiornamento: 10 luglio 2001 5 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Fasi fondamentali dell’esecuzione di una istruzione Prima ancora di discutere il modo con cui viene effettuato il controllo di flusso nel DLX, è opportuno analizzare con sufficiente dettaglio le fasi di esecuzione delle istruzioni. A tal proposito, se escludiamo le istruzioni per la virgola mobile, l’esecuzione di tutte le istruzioni del DLX può essere suddivisa in 5 fasi fondamentali: 1. prelievo dell’istruzione dalla memoria (IF); 2. decodifica dell’istruzione (ID); 3. esecuzione dell’istruzione (EX); 4. accesso alla memoria (MEM); 5. scrittura del risultato (WB). Ogni fase può impiegare uno o più cicli di clock, con riferimento ovviamente all’architettura del DLX proposta in precedenza: per semplicità, escludendo le istruzioni per la virgola mobile, supporremo d’ora in avanti che ogni fase richieda esattamente un ciclo di clock ( 4). Vediamo allora di descrivere le singole fasi: 1. fase di prelievo dell’istruzione dalla memoria (fase IF) Usando la notazione simbolica che ci siamo abituati ad usare dal precedente capitolo, possiamo descrivere questa fase mediante due semplici "operazioni elementari": MAR←PC; IR←M[MAR] La prima operazione consiste dunque nel caricare il registro MAR (che contiene sempre l’indirizzo della parola di memoria cui si vuole accedere, in lettura o scrittura) con il contenuto del registro PC, ossia con l’indirizzo dell’istruzione da prelevare e poi eseguire; tale indirizzo viene dunque usato per indirizzare la memoria e caricare l’istruzione nel registro IR. E’ importante notare che il contenuto del registro PC deve essere caricato nel registro MAR, in quanto, come si vede dall’architettura del DLX, solo il MAR è “collegato” alla memoria, mentre invece il PC no; 2. fase di decodifica dell’istruzione e di preparazione degli operandi (fase ID) Questa fase prevede che l’istruzione venga decodificata (tramite il suo codice operativo) e che si acceda al banco di registri per leggere gli operandi coinvolti nell’istruzione. Anche in questo caso, bastano poche istruzioni simboliche per descrivere le operazioni da compiere: 4 Questa ipotesi parte in effetti da altre ipotesi a monte: ad esempio, vedremo più avanti che, in presenza di una memoria cache interposta tra processore e memoria centrale, la lettura di un valore dalla memoria richiede un ciclo di clock solo se il dato si trova nella memoria cache, mentre i cicli di clock richiesti aumentano se il dato deve essere preso dalla memoria centrale. Autore: Sandro Petrizzelli 6 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori A←Rs1; B←Rs2 PC←PC+4 Da notare l’ultima istruzione, che provvede all’incremento del PC di 4 byte (dato che tutte le istruzioni del DLX hanno lunghezza di 4 byte). Inoltre, con Rs1 ed Rs2 sono stati indicati due qualsiasi registri del banco di registri a disposizione del DLX. E’ importante notare che la decodifica dell’istruzione e le tre operazioni descritte poco fa avvengono qualunque sia il tipo di istruzione in questione: questo è possibile proprio perché gli identificatori dei registri hanno sempre la stessa posizione in tutte le istruzioni, in modo che la lettura avvenga senza ambiguità (questa tecnica è nota come decodifica a campi fissi). Naturalmente, la lettura potrebbe anche risultare inutile dopo che è stata completata la decodifica, ma, non essendo distruttiva né richiedendo tempo addizionale rispetto a quello della decodifica, vale comunque la pena di effettuarla. Si tratta dunque di un primo banale metodo di ottimizzazione del ciclo di esecuzione. Affinché la lettura dei registri Rs1 ed Rs2 avvenga in parallelo con la decodifica dell’istruzione, i valori dei due registri vengono inviati ai latch di uscita A e B prima che l’istruzione venga decodificata. Non solo, ma, dato che anche la parte dell’istruzione contenente un valore immediato è sempre la stessa in ogni formato di istruzione del DLX, l’estensione in segno del valore immediato si può calcolare anch’essa durante questa fase, a prescindere dal fatto che venga richiesta nella fase successiva oppure no. In generale, dunque, si applica il concetto per cui, nonostante alcune operazioni dipendano dall’esito della decodifica, è comunque opportuno effettuarle in parallelo alla decodifica stessa, in quanto questa scelta non ha alcuna controindicazione e può consentire, qualora queste operazioni risultino effettivamente necessarie, di migliorare le prestazioni; 3. fase di esecuzione e di indirizzamento (fase EX) In questa fase (la più critica e spesso più lunga dell’intero processo), risulta sempre coinvolta l’ALU, la quale elabora gli operandi predisposti nella fase precedente ed esegue una tra le seguenti tre funzioni, a seconda del tipo di istruzione: • riferimento alla memoria ( 5); • funzione dell’ALU; • diramazione/salto. Esaminiamo ciascuna di tali funzioni: 5 Ricordiamo che il DLX è una architettura a registri generali di tipo load/store, il che significa che le uniche istruzioni di accesso alla memoria sono quelle per il caricamento dei dati nei registri (load) e per la memorizzazione dei dati in memoria (store). Non sono invece ammesse operazioni ALU direttamente su operandi in memoria, ma solo operazioni su operandi contenuti nei registri. aggiornamento: 10 luglio 2001 7 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Riferimento alla memoria (load/store): MAR ← A + (IR 16 ) 16 ##IR 16..31 ; MDR ← Rd In questo caso, l’istruzione da eseguire è in formato I, per cui presenta un registro base (Rs1,) precedentemente letto e caricato nel latch A, un registro destinazione (Rd), usato solo nel caso di memorizzazione, ed un operando immediato corrispondente ad uno spiazzamento da sommare al registro base; quest’ultimo occupa gli ultimi 16 bit dell’istruzione, ossia quindi gli ultimi 16 bit del registro IR. Allora, il comportamento del processore è il seguente: nell’arco di un unico colpo di clock, estende in segno ( 6) l’operando immediato situato negli ultimi 16 bit del registro IR, lo somma all’operando precedentemente reso disponibile ( in modo da ottenere l’indirizzo effettivo), quindi carica tale indirizzo nel registro MAR ( 7). A questo punto, il processore si potrebbe anche fermare, ma in realtà è opportuna una ulteriore ottimizzazione: nel caso in cui l’istruzione in corso di esecuzione sia una memorizzazione (cioè il trasferimento di un dato da un registro destinazione Rd verso la memoria), risulta utile inizializzare sin da ora il registro MDR con il dato contenuto in Rd. Nel caso, invece, l’istruzione preveda un caricamento (cioè dalla memoria a MDR e da qui in Rd), allora l’ottimizzazione sarebbe inutile ma comunque a costo zero. Funzione dell’ALU: UscitaALU ← A op (B oppure (IR 16 ) 16 ##IR 16..31 ); In questo caso, l’istruzione da eseguire richiede un calcolo (simboleggiato dall’operatore generico op) specificato dal codice operativo; tale calcolo può coinvolgere o meno un operando immediato: se è così , esso occupa gli ultimi 16 bit dell’istruzione, ossia quindi gli ultimi 16 bit del registro IR, mentre invece, in caso contrario, si trovava nel banco di registri ed è stato reso disponibile nel latch B così come l’altro operando nel latch A. Quindi, il processore esegue l’operazione specificata (usando appunto, come secondo operando, o il contenuto di B oppure l’estensione in segno del registro IR) e memorizza il risultato in C (uscita dell’ALU). 6 L’estensione in segno è notoriamente una operazione che aumenta la dimensione (in termini di numero di bit) di un numero, senza modificarne il valore: nel caso di numeri segnati, con quelli negativi espressi in complemento a 2, l’estensione prevede che a sinistra del numero vengano introdotti un certo numero di bit uguali al bit più significativo del numero originale; tale bit vale evidentemente 0 per i numeri positivi ed 1 per i numeri negativi. Ad esempio, dato il numero positivo 00011101, la sua estensione in segno a 16 bit è 0000000000011101. Se invece il numero fosse 10011101 (negativo), la sua estensione in segno a 16 bit sarebbe 1111111110011101. 7 Segnaliamo che la procedura di “estensione in segno” del contenuto del registro IR viene effettuata da una apposita circuiteria (logica dedicata) di tipo combinatorio, posta in uscita al registro IR. Autore: Sandro Petrizzelli 8 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Diramazione/Salto: Diramazione: UscitaALU ← PC + (IR 16 ) 16 ##IR 16..31 ; cond ← (A op 0) Salto: UscitaALU ← PC + (IR 26 ) 6 ##IR 6..31 ; PC ← UscitaALU Quando l’istruzione è una diramazione, bisogna calcolare l’indirizzo di destinazione e “prepararsi” a decidere se effettuare o meno il salto. Lo spiazzamento da sommare al contenuto del registro PC è lungo 16 bit (formato I) nel caso delle diramazioni oppure 26 bit (formato J) nel caso dei salti, sempre con segno: nel caso della diramazione, esso è contenuto nei bit da 16 a 31 del registro IR e quindi, prima di sommarlo al contenuto di PC, deve esserne fatta una estensione in segno di 16 bit; invece, nel caso dei salti, esso è contenuto nei bit da 6 a 31 del registro IR e quindi deve esserne fatta una estensione in segno di 6 bit. Sempre con riferimento alle diramazioni, si nota che viene scritto un registro denominato genericamente “cond”, che appunto servirà nella fase successiva per decidere se l’indirizzo di destinazione deve essere caricato o meno nel registro PC. Nel caso del DLX, le uniche due istruzioni di diramazione confrontano il registro sorgente specificato con 0, per cui l’operatore di confronto op (specificato dal codice operativo) può essere solo di uguaglianza (se l’istruzione è BEQZ) oppure di disuguaglianza se l’istruzione è BNEZ). Notiamo inoltre che anche l’istruzione di scrittura del registro “cond” richiede l’uso dell’ALU, che quindi viene impegnata due volte. Se l’istruzione è un salto (quindi non sottoposto ad alcuna condizione), successivamente al calcolo dell’indirizzo di destinazione viene direttamente aggiornato il registro PC (il che invece non avviene, per le diramazioni, in questa fase) e non ci sono altre operazioni da compiere, per cui le successive due fasi saranno “inattive”. E’ importante sottolineare che, sia nel caso delle diramazioni sia in quello dei salti, lo spiazzamento che viene sommato al registro PC tiene conto del fatto che tale registro è stato già incrementato di 4 byte nella fase precedente, per cui l’indirizzo di destinazione risulta essere PC+4+spiazzamento. Infine, segnaliamo che l’architettura load/store del DLX permette che la fase di calcolo dell’indirizzo effettivo e quella di esecuzione siano riunite in una fase unica, dato che nessuna istruzione si trova a dover calcolare l’indirizzo e contemporaneamente eseguire una operazione su dei dati. 4. fase di accesso alla memoria o completamento della diramazione (fase MEM) E’ subito importare precisare che non tutte le istruzioni risultano attive in questa fase: lo sono solo le istruzioni che fanno riferimento alla memoria. In particolare, esistono due distinte possibilità, a seconda che l’istruzione in corso di esecuzione preveda un caricamento/memorizzazione oppure una diramazione. Esaminiamo ciascuna di tali possibilità: aggiornamento: 10 luglio 2001 9 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Riferimento alla memoria (load/store): MDR ← M[MAR] oppure M[MAR] ← MDR In questo caso, l’istruzione prevede un trasferimento di dati tra memoria e registri: se si tratta di un caricamento (load), i dati vengono prelevati dalla memoria e trasferiti in MDR, mentre invece, in caso contrario (memorizzazione, store), i dati vengono posti in memoria prelevandoli dall’MDR (in cui sono stati posti preventivamente nella fase precedente). In ogni caso, l’indirizzo di memoria usato è quello calcolato nella fase precedente e conservato nel MAR. In ogni caso, il processore si preoccupa di inviare, tramite il bus di sistema, un segnale alla memoria per indicare se vuole leggere o scrivere un dato. Diramazione: if (cond) PC ← UscitaALU Se l’istruzione corrisponde ad una diramazione e la condizione di diramazione risulta verificata (il che equivale a richiedere che il registro di condizione “cond”, inizializzato nella fase precedente, assuma il valore true), l’attuale contenuto del registro PC (che in questo momento corrisponde all’indirizzo dell’istruzione in corso sommato a 4) viene sostituito dall’indirizzo di destinazione della diramazione, calcolato nella fase precedente e quindi prelevato dalla uscita della ALU. 5. fase di scrittura del risultato (fase WB) Rd ← UscitaALU oppure MDR L’ultimo passo dell’esecuzione corrisponde nel porre il “risultato finale” nel registro di destinazione Rd; tale risultato finale può provenire direttamente dall’uscita della ALU (per le istruzioni di tipo aritmetico/logico) oppure dal registro MDR (per le istruzioni di caricamento dalla memoria). Anche per questa fase, è opportuno precisare che non tutte le istruzioni vi risultano attive: ad esempio, non lo sono le istruzioni di memorizzazione (store) e quelle di salto. Prima di andare oltre, riteniamo opportuno osservare una cosa: consideriamo l’istruzione riportata poco fa nell’ultima fase dell’esecuzione, ossia Rd ← MDR (si suppone evidentemente che il dato da portare nel registro Rd si trovi in MDR, ossia sia stato prelevato dalla memoria). Questa scrittura (al pari di tutte le altre precedentemente riportate) in realtà sottintende una serie di operazioni più semplici, che sono le seguenti: MDR → S1 S1 → ALU → Dest Dest → C C → Rd Autore: Sandro Petrizzelli 10 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Il dato contenuto in MDR, per arrivare al registro di destinazione Rd (nel banco dei registri), deve fare un lungo “giro” nell’unità di calcolo, passando dal bus S1 al bus Dest (tramite la ALU) e poi da questo al latch C, il quale ci occuperà di caricare Rd. Non esiste infatti nessun altro “percorso diretto” che colleghi MDR ed Rd. Un altro esempio semplice è quello che corrisponde all’istruzione simbolica MAR ← A + (IR 16 ) 16 ##IR 16..31 ; esaminata anch’essa relativamente alla terza fase dell’esecuzione ed equivalente alla successione delle seguenti operazioni elementari: A → S1 (IR 16 ) 16 ##IR 16..31 → S2 S1 + S2 → Dest Dest → MAR Da notare che l’estensione in segno del valore immediato prelevato dal registro IR avviene direttamente sul bus, usando dei latch appositi. Vediamo invece adesso un esempio più completo, corrispondente all’esecuzione della seguente istruzione: ADD R1,R2,R3 La sequenza di operazioni elementari è la seguente: − − − − − − − − − − − − − −1° ciclo di clock R2 → A → S1 R3 → B → S2 PC = PC + 4 − − − − − − − − − − − − − −2° ciclo di clock ALU S1 + S2 → Dest Dest → C − − − − − − − − − − − − − −3° ciclo di clock C → R1 Il primo ciclo di clock (si trascura la fase precedente di prelevamento dell’istruzione, che richiede un ulteriore ciclo di clock) corrisponde dunque alla decodifica dell’istruzione (fase ID); il ciclo di clock successivo corrisponde all’esecuzione vera e propria (fase EX); il terzo ciclo di clock corrisponde alla scrittura del risultato a destinazione (fase WB), che in questo caso è il registro R1. Evidentemente, questa sequenza vale per qualunque operazione aritmetica o logica e non certo solo per l’istruzione di somma. Ripetiamo ora lo stesso esempio, considerando però l’uso di un operando immediato, pari ad esempio a 5: ADD R1,R2,#5 La sequenza di operazioni elementari è la seguente: aggiornamento: 10 luglio 2001 11 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 − − − − − − − − − − − − − −1° ciclo di clock R2 → A → S1 (IR 16 )16 # # IR 16...31 → S2 PC = PC + 4 − − − − − − − − − − − − − −2° ciclo di clock S1 + S2 ALU → Dest Dest → C − − − − − − − − − − − − − −3° ciclo di clock C → R1 La differenza con l’esempio precedente è evidente, in quanto, al posto di caricare il bus S2 con il valore contenuto nel registro R3, in questo caso si considera il contenuto del registro IR corrispondente ai 16 bit meno significativi, ovviamente esteso in segno. Adesso consideriamo una istruzione di salto, come ad esempio la seguente: JMP #disp Si tratta di una istruzione di salto incondizionato, che riporta uno spiazzamento (displacement) da sommare al registro PC per ottenere l’indirizzo della prossima istruzione da eseguire. Le sequenza delle “operazioni” compiute (trascurando sempre la fase iniziale di prelievo dell’istruzione), è dunque la seguente: − − − − − − − − − − − − − −1° ciclo di clock → S1 PC → S2 (IR 6 ) 6 # # IR 6...31 PC = PC + 4 − − − − − − − − − − − − − −2° ciclo di clock S1 + S2 ALU → Dest → PC Dest Sono dunque sufficienti due soli cicli di clock: il primo serve alla “preparazione” degli operandi (fase ID), mentre il secondo serve per il calcolo dell’indirizzo di salto e la sua immediata scrittura nel registro PC (fase EX). Sarebbe invece necessario un altro ciclo di clock nel caso di diramazione (ad esempio BEQZ R1,#disp): bisognerebbe infatti verificare la condizione del salto e, in caso affermativo, aggiornare il valore del registro PC per completare il salto (fase MEM). L Laatteen nzzaa d deellllee iissttrru uzziioon nii I discorsi fatti nell’ultimo paragrafo ci consentono di fare alcune importanti osservazioni circa la “latenza” delle istruzioni, ossia il tempo totale necessario per la loro esecuzione. In linea generale, è importante comprendere che le istruzioni hanno durata diversa a seconda delle fasi in cui sono effettivamente attive ; non solo, ma, Autore: Sandro Petrizzelli 12 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori sempre in linea generale, le singole fasi possono a loro volta richiede uno o più cicli di clock. Nel caso del DLX, tuttavia, abbiamo supposto in precedenza che tutte le istruzioni, tranne quelle per la virgola mobile, prevedano stadi di esecuzione ciascuno di un unico ciclo di clock, per cui l’aspetto su cui dobbiamo concentrarci è proprio il numero di stadi in cui le varie istruzioni risultano attive. Consideriamo ad esempio una istruzione di lettura da memoria (load): questa è l’istruzione più lunga possibile, dato che richiede il compimento di operazioni in tutte le cinque fasi (prelievo, decodifica, esecuzione, accesso alla memoria, scrittura dei risultati), mentre invece i salti sono le istruzioni più veloci, in quanto non richiedono né l’accesso alla memoria dati (fase MEM) né la scrittura del banco di registri (fase WB). Al fine di avere una idea ancora più concreta, immaginiamo che gli accessi a memoria (fasi IF e MEM), l’uso della ALU e l’uso di addizionatori (fase EX) richiedano ognuno 10 ns e che gli accessi al banco dei registri (fasi ID e WB) richiedano 5 ns; sulla base di tali dati, andiamo a confrontare la durata di istruzioni delle varie classi, considerando in particolare le seguenti: • load word (LW): lettura da memoria e scrittura in un registro; • store word (SW): lettura da registro e scrittura in memoria; • add e sub (ADD e SUB): addizione e sottrazione in aritmetica intera; • and e or (AND e OR): operazioni logiche; • jump (J): salto (incondizionato) ad un indirizzo di destinazione (specificato nell’istruzione stessa); • branch on equal zero (BEQZ): salto ad un indirizzo di destinazione (specificato nell’istruzione stessa) se il contenuto del registro specificato è pari a 0. La tabella seguente mostra la latenza per queste istruzioni: Istruzione LW SW BEQZ ALU J IF 10 ns 10 ns 10 ns 10 ns 10 ns ID 5 ns 5 ns 5 ns 5 ns 5 ns EX 10 ns 10 ns 10 ns 10 ns 10 ns MEM 10 ns 10 ns 10 ns WB 5 ns 5 ns totale 40 ns 35 ns 35 ns 30 ns 25 ns Quando affronteremo il problema del pipelining, vedremo che una condizione necessaria per l’esecuzione di più istruzioni in contemporanea è che per ogni istruzione vengano comunque eseguiti tutti gli stadi del ciclo di esecuzione, anche se in alcuni stadi non vengono effettuate operazioni di alcun tipo, e che la latenza di tutti gli stadi sia resa uguale alla latenza dello stadio più lento. Ad esempio, se i valori di latenza dei vari stadi fossero quelli della tabella di poco fa, dovremmo imporre a tutti gli stadi una durata di 10 ns, nonostante lo stadio ID e lo stadio WB, quando attivi, richiedano invece solo 5 ns. aggiornamento: 10 luglio 2001 13 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 IIll ““cco on nttrro ollllo o”” Introduzione Abbiamo già osservato in precedenza che l’unità di calcolo è quella parte di processore più difficile da progettare. Il progetto di questa unità deve partire dalla definizione di due “oggetti”: l’insieme delle istruzioni della macchina da realizzare (ad esempio il DLX) e lo schema dell’unità di calcolo. L’unità di calcolo è costituita da un certo numero di componenti hardware fondamentali (registri, bus, ALU, circuiti combinatori di vario tipo), opportunamente connessi tra di loro e ciascuno con proprie funzionalità. Vogliamo allora vedere il modo in cui tali componenti hardware vengono “pilotati”, ossia come ne vengono gestite le “operazioni”, quando c’è da eseguire una istruzione. Ad esempio, vogliamo vedere come viene avviato il caricamento di un dato in un registro, la memorizzazione di un dato da un registro su un bus, il compimento di una operazione da parte della ALU e così via. Vogliamo cioè esaminare la questione del cosiddetto controllo, inteso sostanzialmente come controllo dell’unità di calcolo. Diagramma a stati finiti descrittivo del controllo L’azione di controllo, in un processore, viene evidentemente svolta dall’unità di controllo situata in esso: l’unità di controllo “informa” l’unità di calcolo sulle attività da eseguire in ogni ciclo di clock durante l’esecuzione delle istruzioni. Dal nostro punto di vista, possiamo “vedere” tali attività specificate tramite un diagramma a stati finiti: ogni stato del diagramma corrisponde ad un ciclo di clock e tutte le operazioni da eseguire durante tale ciclo sono specificate dallo stato. Affinché l’esecuzione di ogni istruzione sia portata a termine, servono sempre diversi cicli di clock ( 8). Tanto per chiarirci le idee sul diagramma a stati finiti rappresentativo del controllo, consideriamo come esempio le prime due fasi dell’esecuzione delle istruzioni del DLX, che abbiamo descritto in precedenza tramite le seguenti sequenze di operazioni elementari: MAR←PC; IR←M[MAR] A←Rs1; B←Rs2 PC←PC+4 Il corrispondente diagramma a stati finiti è il seguente: 8 Quando parleremo di pipeline, vedremo un metodo con cui sovrapporre l’esecuzione di più istruzioni, al fine di eseguire una istruzione per ogni ciclo di clock. Autore: Sandro Petrizzelli 14 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori MAR ç PC Accesso alla memoria incompleto IR ç M[MAR] Accesso alla memoria completo PC ç PC + 4 A ç Rs1 B ç Rs2 Parte iniziale del diagramma a stati finiti per l’esecuzione di una istruzione. Sono mostrate le prime due fasi dell’esecuzione delle istruzioni: il prelievo dell’istruzione dalla memoria e la decodifica dell’istruzione con la lettura dei registri. Da notare che il secondo stato viene ripetuto fin quando l’istruzione viene interamente prelevata interamente dalla memoria. Si nota che la prima fase (prelievo in memoria della istruzione da eseguire) risulta “distribuita” su tre stati, ossia impiega tre cicli di clock: nel primo ciclo, il MAR viene caricato con l’indirizzo contenuto nel PC; nel secondo ciclo, l’istruzione prelevata dalla memoria viene posta nel registro IR; infine, nel terzo ciclo il registro PC viene aggiornato in modo da puntare all’istruzione successiva. Nello stesso ciclo, però, viene effettuata anche la seconda fase dell’esecuzione, in quanto vengono caricati i due operandi dai registri Rs1 ed Rs2 rispettivamente nei registri A e B, che saranno usati nelle fasi successive. Facciamo notare che il passaggio dal secondo al terzo stato del diagramma richiede che l’accesso alla memoria sia completo, ossia evidentemente che l’istruzione richiesta venga interamente caricata nel registro IR. Questo aspetto sarà approfondito più avanti. Una volta noto il diagramma a stati, il passo successivo consiste nel “trasformarlo” in una struttura fisica e qui si presentano varie alternative, in base alla tecnologia di realizzazione, che esamineremo nei prossimi paragrafi. Un semplice modo per valutare la complessità dell’organo di controllo corrisponde a calcolatore il seguente prodotto: Stati × Ingressi di controllo × Uscite di controllo In questa espressione: • “Stati” indica il numero di stati dell’unità di controllo • “Ingressi di controllo” è il numero di segnali esaminati dall’unità di controllo; • “Uscite di controllo” è il numero di uscite di controllo generate dall’unità di controllo, inclusi i bit che specificano lo stato successivo (si veda più avanti). aggiornamento: 10 luglio 2001 15 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Controllo dedicato (nel DLX) Nella prossima figura viene schematizzata l’organizzazione del controllo per il DLX nel caso si decida di usare una logica dedicata, il che significa implementare le funzioni di controllo direttamente in hardware: Unità di calcolo Unità di controllo Logica dedicata 40 bit 2(12+3+6)=221 elementi 12 bit 6 bit Uscite di controllo (40 bit) 3 bit Stato Registro Istruzioni Controllo dedicato di un insieme semplice di istruzioni, specificato tramite una tabella Vediamo di capire il perché di una struttura di questo tipo. Si può verificare che il diagramma completo degli stati del DLX contiene circa 50 stati diversi : • per codificare in binario ciascuno di tali stati , sono necessari 6 bit, dato che 2 6 =64>52 (e 2 5 =32<50); l’organo di controllo riceve quindi in ingresso 6 linee di controllo, che descrivono lo stato attuale ( 9); • a queste linee di controllo se ne aggiungono necessariamente delle altre: alcune linee provengono dall’unità di calcolo (ad esempio le linee di condizione in uscita dalla ALU, per segnalare un overflow oppure un risultato negativo o nullo o altro) e dall’unità di interfacciamento della memoria (ne sono state considerate 3 in tutto nella figura), mentre altre linee devono provenire dal registro istruzioni (IR), per indicare il tipo di istruzione da eseguire di volta in volta: in particolare, si tratta di 12 linee, di cui 6 corrispondono al codice operativo primario e altre 6 a quello esteso ( 10); • l’unità di controllo ha poi delle linee di uscita, dirette appunto all’unità di calcolo, che esercitano materialmente l’attività di controllo: l’unità di calcolo implementa (in hardware) una specie di tabella di grandi dimensioni; gli ingressi di controllo, nel loro complesso, individuano univocamente una riga di tale tabella, la quale riga contiene a sua volta i valori delle linee di uscita 9 La logica combinatoria dell’unità di controllo, insieme allo stato che la retroaziona, costituisce per definizione un circuito sequenziale. 10 Gli indicatori dei registri e gli operandi immediati situati nel registro IR non hanno invece motivo di passare per l’unità di calcolo e quindi vengono inviati direttamente all’unità di calcolo. Autore: Sandro Petrizzelli 16 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori che pilotano, nell’unità di calcolo, le operazioni richieste dallo stato e forniscono il numero dello stato successivo. Nella figura sono state indicate 40 uscite di controllo, ritenute sufficienti. Tanto per fare un esempio semplice della funzione svolta dalle uscite di controllo, soffermiamoci per un attimo sul registro PC dell’unità di calcolo. Ci si può rendere conto facilmente che, relativamente all’architettura del DLX, tale registro deve essere “controllato” da tre linee: una linea deve stabilire se, in un dato istante, il registro deve acquisire il dato attualmente contenuto nel bus Dest; le altre due linee, invece, devono specificare se il dato contenuto nel registro deve essere trasferito sul bus S1 o sul bus S2. Il discorso è ovviamente del tutto analogo per gli altri registri speciali (MAR, MDR, IAR, Temp). In modo del tutto analogo, il multiplexer che si trova in ingresso al registro MDR necessita di una sola linea di controllo, che selezioni il dato da portare nel registro MDR: può trattarsi dell’uscita della ALU (situata sul bus Dest) oppure di un dato proveniente dalla memoria (tramite il bus dati). Se invece consideriamo i latch A, B e C, per ciascuno di essi basterà una sola linea: ad esempio, ogni volta che vogliamo acquisire un dato nel latch C (dal bus Dest), dobbiamo asserire la corrispondente linea di controllo, in modo appunto da avviare l’acquisizione. R Riid du uzziioon nee d deeii ccoossttii d deellll’’h haarrd dw waarree d deell ccoon nttrroolllloo d deed diiccaattoo E’ noto che la realizzazione più semplice di una tabella si ottiene tramite una memoria ROM: le linee di ingresso individuano una riga della memoria, la quale produce una data configurazione per le linee di uscita. Tuttavia, il problema è che, nell’esempio considerato nel paragrafo precedente, sarebbero necessarie 2 21 parole, ognuna da 40 bit; si otterrebbe una memoria ROM da 10 MB, il che è decisamente improponibile. Fortunatamente, solo una parte limitata di questa tabella memorizza informazioni non ridondanti, per cui è possibile ridurne le dimensioni conservando solo le righe non ridondanti ed eliminando tutte le altre (le cosiddette condizioni don’t care della tabella di verità della ROM), a costo naturalmente di una decodifica più complicata degli indirizzi della tabella. Un dispositivo hardware che assolve a questa funzione è una matrice logica programmabile (PLA, Programmable Logic Array): tramite una PLA, si riesce a ridurre le dimensioni dell’hardware da 2 21 parole ad appena 50, aumentando però le dimensioni e la complessità del dispositivo per la decodifica degli indirizzi. Nelle macchine reali, però, anche una sola PLA risulta talvolta proibitiva, in quanto le sue dimensioni crescono in modo proporzionale al prodotto delle righe non ridondanti con la somma degli ingressi e delle uscite. Per aggirare il problema, la tabella complessiva viene scomposta in tabelle più piccole, ognuna implementata con una PLA: le uscite delle varie PLA vengono poi commutate per scegliere il controllo corretto. P Prreessttaazziioon nii d deell ccoon nttrroolllloo d deed diiccaattoo Quando si progetta in dettaglio il controllo di una macchina, gli obbiettivi che si vogliono perseguire sono i seguenti: • minimizzazione del valore medio del CPI (cicli di clock per istruzione); aggiornamento: 10 luglio 2001 17 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 • minimizzazione della durata del periodo di clock; • minimizzazione del numero di dispositivi hardware richiesti dal controllo; • minimizzazione del tempo necessario a sviluppare un organo di controllo che funzioni nel modo desiderato. Il primo obbiettivo si può ad esempio ottenere riducendo il numero medio di stati che costituiscono il percorso di esecuzione di una istruzione, dato che ciascuno stato corrisponde ad un ciclo di clock; a sua volta, la riduzione del numero medio di stati viene ottenuta modificando opportunamente l’unità di calcolo, al fine di riunire o addirittura eliminare determinati stati. Controllo microprogrammato Nei primi calcolatori, le istruzioni per l’aritmetica, le operazioni booleane, lo scorrimento (shifting), il confronto, i cicli, ecc. erano direttamente eseguite dall’hardware: per ciascuna istruzione esisteva cioè uno specifico circuito hardware addetto alla sua esecuzione. Si poteva addirittura svitare il pannello posteriore del contenitore (il cosiddetto case) del calcolatore e individuare le componenti elettroniche usate per ciascuna operazione. In un calcolatore moderno a più livelli, invece, non esistono più circuiti per ogni singola operazione: tutte le istruzioni disponibili al livello della macchina standard (cioè appunto le istruzioni per l’aritmetica, per le istruzioni booleane, per lo shifting e così via) sono eseguite, un passo alla volta, tramite un cosiddetto interprete, che lavora al livello della microprogrammazione (livello L1). In generale, un interprete è un programma (quindi si tratta di software), scritto nel linguaggio del livello a cui lavora (ad esempio L1), che riceve in input i programmi scritti nel linguaggio di livello superiore (L2) e li esegue, esaminando una istruzione dopo l'altra ed eseguendo, per ognuna, la sequenza equivalente di istruzioni in L1. Quindi, mentre nei vecchi calcolatori si potevano ad esempio individuare i circuiti hardware dedicati appositamente alla divisione, l’equivalente nei moderni calcolatori sarebbe quello di individuare il listato del microprogramma eseguito dall’interprete per interpretare le istruzioni di divisione. Anche se i programmi di un qualsiasi livello possono essere eseguiti da un interprete e quest’ultimo può essere eseguito a sua volta da un interprete ancora di livello inferiore, è ovvio che tale gerarchia non può procedere all’infinito (andando verso il basso): al livello più basso deve infatti esistere una macchina fisica, con circuiti hardware, in grado di compiere le operazioni richieste dalle istruzioni. Dato che l’architettura del livello della microprogrammazione, chiamata microarchitettura, è definita dall’hardware, solitamente risulta abbastanza difficile da programmare. Il lavoro del microprogrammatore è quello di scrivere microprogrammi, ossia programmi per controllare i registri, i bus, le ALU, le memorie e gli altri componenti hardware della macchina. A tal fine, risulta indispensabile la conoscenza di tutti questi dispositivi ( 11). 11 A tali dispositivi è dedicata una apposita appendice Autore: Sandro Petrizzelli 18 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori L Lee m miiccrrooiissttrru uzziioon nii Le microistruzioni hanno fondamentalmente due compiti: • specificano tutti i segnali di controllo per l’unità di calcolo; • decidono, in modo condizionato (ossia sulla base del verificarsi di determinati “eventi” nonché sulla base dello “stato” della macchina), quale istruzione debba essere eseguita in seguito. Una volta che sono state progettate l’unità di calcolo e la memoria delle microistruzioni (ossia il chip in cui memorizzare tutte le microistruzioni a disposizione della macchina), il controllo diventa una attività tipicamente di programmazione, come del resto suggerisce lo stesso termine “microprogrammazione”: questo significa, sostanzialmente, che bisogna scrivere un interprete per l’insieme di istruzioni, ossia un programma che associ, ad ogni istruzione, la corrispondente sequenza di microistruzioni (ossia il corrispondente microprogramma). A sua volta, ogni microistruzione fa in modo che l’unità di calcolo esegua un certo numero di microperazioni. La figura seguente riporta un esempio del modo in cui può essere organizzato un semplice controllo microprogrammato: Unità di controllo Memoria di Controllo Regist ro Micro istruzi oni Unità di calcolo Uscite di controllo (40 bit) Registro Contatore di Microprogramma 1 + Circuito di selezione dell'indirizzo Una unità di controllo microprogrammati semplice Quanto riportato in questa figura va ovviamente confrontato con la figura precedente, in cui abbiamo riportato un esempio di unità di controllo dedicata. La differenza è abbastanza evidente: l’unità di controllo è costituita da una classica “struttura” per la decodifica e l’esecuzione di istruzioni, che in questo caso sono ovviamente microistruzioni; tale struttura è composta da: • 12 una memoria contenente le microistruzioni a disposizione della macchina (memoria di controllo); ( 12) Si tratta generalmente di una EPROM o di una EAROM, comunque di una memoria a sola lettura. aggiornamento: 10 luglio 2001 19 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 • un registro contenente microistruzioni), • un incrementatore: si tratta di una particolare unità aritmetica e logica, avente la particolarità di ricevere in ingresso un operando variabile ed una costante pari ad 1 e di eseguire solo l’operazione di somma tra di essi; • un circuito speciale che serve per specificare la microistruzione successiva (contatore di microprogramma e circuito di selezione dell’indirizzo). la microistruzione da eseguire (registro Il funzionamento del controllo microprogrammato è tale per cui, in uscita dal registro microistruzioni, partano i segnali di controllo (40 nel nostro esempio) che vanno a controllare e pilotare il funzionamento dell’unità di calcolo, così come accade anche nel controllo dedicato. F Foon nd daam meen nttii d deellllaa m miiccrroop prrooggrraam mm maazziioon nee L’uscita del registro microistruzioni è dunque costituita da un certo numero di linee di controllo che si dirigono verso l’unità di calcolo e ne regolano e controllano il comportamento. Ogni linea ha un compito ben preciso: ci sono ad esempio le linee che impongono il caricamento di un registro oppure la memorizzazione del suo contenuto in un bus, linee che vanno direttamente in ingresso alla ALU dell’unità di calcolo e specificano l’operazione da compiere ( 13), linee che pilotano i latch in ingresso ed in uscita alla predetta ALU e così via. Se interpretiamo ciascuna linea come un bit (che può valere 0 oppure 1 a seconda che sia asserita o meno), l’insieme delle linee costituisce una microistruzione. In linea di massima, dato che ciascuna linea ha un compito ben preciso, dal punto di vista dell’hardware risulta indifferente il modo in cui raggruppiamo le linee per formare una microistruzione; al contrario, per rendere più “leggibile” il significato delle microistruzioni, ci conviene sistemare sempre vicine le linee che svolgono compiti più o meno correlati: in questo modo, possiamo individuare una struttura ben precisa delle microistruzioni, suddividendole in campi, dove ogni campo va visto come un insieme di linee che svolgono funzioni analoghe. La figura seguente mostra un semplice esempio di microistruzione ad 8 campi: Destinazione Operazione ALU Sorgente 1 Sorgente 2 Costante Misto Condizione Indirizzo di salto Esempio di microistruzione con 8 campi: il nome attribuito ai vari campi riflette evidentemente la loro funzione. Possiamo immaginare la microprogrammazione come un modo per associare, a ciascun campo, una opportuna configurazione di bit, il che somiglia molto alla programmazione di istruzioni in linguaggio macchina ( 14). 13 A tal proposito, ricordiamo una cosa: la ALU è fatta in modo che, dati gli operandi in ingresso, vengano effettuate su di esse TUTTE le operazioni (aritmetiche e logiche) che la ALU è in grado di fare; tuttavia, dato che interessa una sola di queste operazioni per volta, l’ “azione” delle linee di controllo è semplicemente quella di selezionare il risultato di interesse e porlo in uscita dalla ALU. 14 Per distinguere le “microistruzioni” dalle “istruzioni in linguaggio macchina”, queste ultime vengono talvolta chiamate macroistruzioni. Autore: Sandro Petrizzelli 20 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Facendo ancora riferimento al precedente esempio di unità di controllo microprogrammata, osserviamo che esistono due modi distinti per specificare la microistruzione successiva da eseguire: • nell’esempio da noi raffigurato, si è usato un registro contatore di microprogramma (talvolta indicato con µ PC), del tutto analogo al registro PC per le istruzioni in linguaggio macchina: esso contiene l’indirizzo della prossima microistruzione da eseguire; • l’alternativa sarebbe quella di riservare un campo, in ogni microistruzione, in cui riportare l’indirizzo della microistruzione successiva. Questo ovviamente comporta un allungamento dell’istruzione, ma talvolta è una soluzione da preferirsi, tanto che, in alcune macchine, vengono previsti addirittura più campi per l’istruzione successiva, in modo da poter gestire anche le diramazioni. La memoria di controllo, situata nell’unità di controllo microprogrammata, è la parte fisica più appariscente nel controllo microprogrammato ed è per questo che la maggior parte delle tecniche usate per ridurre il costo dell’hardware sono rivolte proprio ad essa. Si possono individuare almeno tre tecniche per ridurre le dimensioni della memoria di controllo: • ridurre il numero di microistruzioni; • ridurre la lunghezza della microistruzioni; • entrambe le cose. Mentre il costo dell’unità di controllo microprogrammata viene quantificato tramite le dimensioni della memoria di controllo, le prestazioni vengono invece generalmente espresse tramite il CPI (che in questo caso corrisponde ai cicli di clock per microistruzione). Ogni microprogrammatore conosce la frequenza con cui si presentano le singole istruzioni in linguaggio macchina e quindi sa dove e come “spendere meglio il tempo”, ottimizzando in velocità le istruzioni che consumano la maggior parte del tempo di esecuzione ed ottimizzando in spazio le istruzioni rimanenti. Esecuzione di una istruzione Abbiamo dunque detto che, in un microprocessore microprogrammato, l’esecuzione di una istruzione letta dalla memoria principale richiede l’esecuzione di un opportuno microprogramma residente nella memoria di controllo : • ogni microprogramma che interpreta una istruzione è composto da microistruzioni; • ogni microistruzione controlla elementari (microoperazioni); • ogni microoperazione è espressa da un microordine rappresentato da un opportuno bit, settato ad 1, nella microistruzione. aggiornamento: 10 luglio 2001 l’esecuzione 21 di una o più operazioni Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 La memoria di controllo è gestita dall’unità di controllo microprogrammata, la quale quindi presiede allo svolgimento delle seguenti operazioni: • in primo luogo, genera l’indirizzo della prima microistruzione del microprogramma relativo all’istruzione appena letta (situata nel registro IR dell’unità di calcolo). Tale indirizzo viene generato dal circuito di selezione dell’indirizzo, sulla base del codice operativo dell’istruzione; • in secondo luogo, genera gli indirizzi delle successive microistruzioni componenti il microprogramma da eseguire. Questa funzione viene realizzata dal registro µPC (micro-program counter), che funziona in modo del tutto analogo al registro PC per le istruzioni in linguaggio macchina. Nel caso più generale, il registro µPC viene autoincrementato per generare l’indirizzo della microistruzione successiva, fino a quando non viene letto il microordine denominato End Of Operation (EOP): questo microordine segnala il termine del microprogramma, ed è seguito dalla microoperazione 0→ → µ PC, che serve per ripartire con la istruzione successiva; in particolare, si suppone che l’indirizzo 0 della memoria di controllo corrisponda a quello del microprogramma che pilota la fase di fetch dell’istruzione successiva . Il contenuto di µPC può del resto essere alterato dal verificarsi di particolari condizioni, segnalate dalla logica di selezione del prossimo indirizzo tramite alcuni bit di controllo: questi bit, quando attivati, possono alterare la condizione di autoincremento di µPC, rendendo così possibili le diramazioni (a livello di microprogramma); • infine, l’unità di controllo attiva le linee di controllo indicate dalla microistruzione in corso di esecuzione, le quali abilitano l’esecuzione dei microordini richiesti per l’esecuzione dell’istruzione attualmente nel registro IR. Per semplificare la realizzazione della memoria di controllo nonché la configurazione delle linee di controllo, i microordini sono raggruppati per categorie e nelle microistruzioni sono organizzati in campi. Scendendo ancora in maggiore dettaglio, l’interpretazione di una istruzione richiede l’esecuzione dei seguenti passi: • l’istruzione da eseguire viene prelevata dalla memoria e portata nel registro IR (fase di fetch); • la logica di selezione dell’indirizzo genera, mediante una tabella di puntatori (detta mapping memory o anche tabella di mapping), l’indirizzo, associato al codice operativo dell’istruzione in IR, della prima microistruzione del microprogramma da eseguire; tale indirizzo viene posto nel registro µPC; Autore: Sandro Petrizzelli 22 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Registro Istruzioni IR Tabella di mapping Contatore di microprogramma MAP Memoria di controllo FW uPC Registro Microistruzioni MIR Schematizzazione del processo di individuazione della prima microistruzione corrispondente all’istruzione contenuta nel registro IR • la microistruzione così indirizzata viene trasferita dalla memoria di controllo nel registro delle microistruzioni (MIR); • viene infine attivata la fase di esecuzione della istruzione, pilotata dalle linee di controllo in uscita dal registro delle microistruzioni. Fondamentalmente, le microistruzioni corrispondono a trasferimento di dati tra registri, specifiche parole di memoria, con o senza il tramite della ALU. Esempio: microprogramma istruzione per la fase di fetch di una Supponiamo che tutte le istruzioni e i dati del nostro calcolatore abbiano la stessa lunghezza ed occupino una sola locazione di memoria principale. Sotto questa ipotesi, la sequenza di microistruzioni (microprogramma) che realizza la fase di fetch di una istruzione è il seguente: PC → MAR M[MAR] → MDR MDR → IR MAP(IR CO ) → µPC La prima microistruzione fa in modo che il contenuto del registro MAR venga caricato con l’indirizzo contenuto nel registro PC. Il contenuto del MAR viene usato dal processore per interfacciarsi al bus degli indirizzi e quindi puntare alle locazioni di memoria principale: la locazione di memoria puntata dal MAR viene quindi caricata, tramite il bus dei dati, nell’MDR, che a sua volta viene “copiato” nel registro IR. Infine, l’ultima istruzione individua, nella tabella di mapping, l’indirizzo della prossima microistruzione da eseguire: tale indirizzo viene ricavato sulla base del codice operativo dell’istruzione appena caricata nel registro IR (da cui la simbologia IR CO ). L’indirizzo ottenuto viene quindi caricato nel registro µPC. Quella appena fatta è una descrizione precisa ma comunque ancora non dettagliatissima di quello che avviene effettivamente nell’unità di calcolo. Infatti, consideriamo ad esempio la prima microistruzione, PC→MAR: non essendoci un collegamento diretto tra il registro PC ed il registro MAR, il passaggio dell’indirizzo avviene tramite la ALU; in particolare, si ha quanto segue: aggiornamento: 10 luglio 2001 23 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 PC → S1 S1 ALU → Dest Dest → MAR Tutte queste microoperazioni sono pilotate sempre dalla microistruzione: come si è già detto, ad ogni microoperazione sono assegnate un certo numero di linee di controllo, le quali, tramite anche apposita circuiteria, non solo pilotano ad esempio il caricamento di un registro su un bus o viceversa, ma anche la temporizzazione. R Riid du uzziioon nee d deeii ccoossttii d deellll’’h haarrd dw waarree Ricordiamo che le tecniche di controllo, sia dedicate sia microprogrammate, sono valutate in base al loro effetto sui seguenti fattori: • costo dell’hardware; • durata del ciclo di clock; • CPI; • tempo di progetto. Per quanto riguarda i costi, abbiamo già osservato che sono determinati in massima parte dalla dimensione della memoria di controllo ed è quindi tale dimensione che deve essere ottimizzata. A tal proposito, si possono sia ridurre il numero di microistruzioni sia ridurre la lunghezza delle microistruzioni sia fare entrambe le cose. Nei prossimi paragrafi vedremo come ottenere questi obbiettivi, mentre nei paragrafi ancora successivi vedremo come ridurre il CPI. Codifica delle linee di controllo L’approccio migliore per ridurre la dimensione della memoria di controllo consiste nei seguenti passaggi: • per prima cosa, bisogna scrivere il microprogramma completo, in notazione simbolica; • successivamente, per ogni istruzione del microprogramma, bisogna esaminare la configurazione dei bit (corrispondente a quella delle linee di controllo); • tramite questo esame e le relative misure, si possono identificare gruppi di bit di controllo che possono essere codificati tramite campi di dimensione inferiore. Ad esempio, abbiamo visto in precedenza che il bus S1 dell’unità di calcolo del DLX può essere alimentato da 6 diversi dispositivi: il latch A e i 5 registri speciali (Temp, PC, IAR, MAR e MDR); in linea di massima, ci servono allora 6 linee di controllo per controllare il caricamento di tale bus; possiamo ridurre tale numero a 3, in quanto, con 3 bit possiamo codificare 2 3 =8 possibilità, ossia due in più rispetto a quelle necessarie. Naturalmente, questa modifica, pur facendo risparmiare 3 bit per istruzione e pur non influendo negativamente sul CPI, Autore: Sandro Petrizzelli 24 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori richiede comunque un costo aggiuntivo dell’hardware, dato che dobbiamo prevedere un circuito decodificatore con 3 ingressi ed 8 uscite (di cui due inutili): x=1 Unità di Controllo y=0 3*23 z=0 0 1 2 3 4 5 6 7 0 0 0 0 1 0 0 0 Unità di Calcolo Decoder 3*8 Uso di un circuito decodificatore 3*23 : la configurazione binaria delle linee in ingresso determina l’unica linea in uscita che deve essere asserita. L’esempio riportato nella figura prevede in ingresso la configurazione 100, per cui attiva la linea di uscita numero 4 Con un simile circuito, la configurazione delle 3 linee di controllo in ingresso determina quale, delle 8 linee di controllo in uscita, debba essere attivata: si tratterà evidentemente di quella che individua il registro con cui caricare il bus S1. Questo incremento di costo per il decodificatore è ampiamente compensato dal risparmio di 3 bit sull’ampiezza della parola di memoria di controllo. La tecnica appena descritta per la riduzione della dimensione dei campi delle microistruzioni prende il nome di codifica delle linee di controllo. Un ulteriore miglioramento si può ottenere individuando eventuali linee che vengono asserite soltanto di rado nella medesima microistruzione: se ci sono, le si può codificare tutte insieme (tramite cioè una sola linea), pagando ovviamente il prezzo che, nei cari rari in cui vengono asserite, servono due microistruzioni anziché una sola. In questo modo, la lunghezza delle microistruzioni viene ridotta, ma ne viene aumentato il numero: se si ottiene, complessivamente, una riduzione della dimensione della memoria di controllo, allora si tratta di una strada sicuramente perseguibile. Le tecniche di codifica delle linee di controllo hanno del resto anche alcuni svantaggi. Ad esempio, se una linea di controllo codificata fa parte di un percorso con ritardo critico oppure se i dispositivi fisici da essa controllati fanno parte di un percorso con ritardo critico, il tempo del ciclo di clock sicuramente ne soffrirà. Un altro svantaggio, meno immediato del precedente, è quello che invece potrebbe verificarsi a seguito dell’introduzione di una nuova versione del microcodice: ad esempio, se tale nuova versione può determinare con più frequenza, rispetto alla versione precedente, situazioni in cui talune linee di controllo codificate vengono attivate nella stessa microistruzione, si rischierebbe di abbassare le prestazioni e/o sarebbero richieste modifiche della struttura hardware, il che potrebbe aumentare la durata del ciclo di sviluppo. Formati multipli di microistruzioni Nei due precedenti paragrafi abbiamo dato per scontato che le microistruzioni avessero un unico formato, per cui non era necessario prevedere un codice operativo che ne identificasse le caratteristiche. Al contrario, un modo per diminuire ulteriormente la dimensione delle microistruzioni è proprio quello di prevedere formati differenti, nel qual caso risulta necessario l’uso di un codice operativo che aggiornamento: 10 luglio 2001 25 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 distingua un formato dall’altro. Per evitare confusione nella terminologia, questo nuovo campo prende il nome di campo di formato. Lo scopo del campo di formato è semplice: dato che ciascun formato potrà usare tutte o parte delle linee di controllo, assegnando valori opportuni alle linee utilizzate, il campo di formato assegna, a tutte le linee non specificate, il loro valore di default, in modo da non dover modificare nient’altro della macchina. Se l’uso del campo di formato consente di ridurre le dimensioni della memoria di controllo, esso richiede altresì un proprio costo in termini di prestazioni, perché vengono eseguite più microistruzioni: infatti, se un microprogramma usa un unico formato di microistruzioni, tale formato può specificare qualsiasi combinazione di operazioni dell’unità di controllo e richiede meno cicli di clock rispetto ad un microprogramma con microistruzioni a formato variabile, che per loro natura possono identificare ciascuna un numero ristretto di operazioni. Del resto, le macchine con parole di memoria di piccole dimensioni risultano sempre più economiche in quanto i chip, pur avendo un numero maggiore di parole, hanno “larghezza” (intesa appunto come dimensione delle parole) minore: ad esempio, servono molti meno chip per una memoria da 16384 parole (16K) da 24 bit che non per una di 4096 prole (4 K) da 96 bit. L’approccio con molte parole di memoria di piccola dimensione viene spesso detto microcodice verticale, mentre invece quello con poche parole ma di grandi dimensioni viene detto microcodice orizzontale. Anche se questa terminologia si può spiegare facilmente, per evitare confusione noi parleremo invece, rispettivamente, di codifica massima e codifica minima. Ovviamente, l’uso di parole piccole di memoria presuppone che i campi delle microistruzioni siano codificati secondo i criteri del paragrafo precedente e che quindi le linee di controllo in uscita dall’unità di controllo vadano a pilotare dei decoder che poi a loro volta pilotano effettivamente l’unità di calcolo. Viceversa, l’uso di parole grandi di memoria equivale all’uso di microistruzioni molto lunghe (molte linee) e quindi non sono necessari i decoder. A volte capita che i progettisti di una macchina a codifica minima non riescano a disporre di memorie RAM con numero piccolo di parole, nel qual caso essi finiscono col progettare microistruzioni di grandi dimensioni che però occupano un gran numero di parole nella memoria di controllo. In questi casi, dato che il costo dell’hardware dipende dal numero e dimensione delle parole e non certo da quante di esse risultano effettivamente occupate, alla fine si rischia di arrivare ad avere memorie di controllo molto più grandi di quanto ci si aspettasse o comunque rispetto ad altri esempi di macchine dello stesso tipo. Aggiunta di microcodice controllo dedicato per la condivisione di Abbiamo già osservato che un altro possibile approccio per la riduzione della memoria di controllo consiste nel diminuire non tanto la dimensione delle microistruzioni, quanto il loro numero. A tal fine, si potrebbe ad esempio ricorrere ai cosiddetti microsottoprogrammi: si tratta di porzioni di microcodice a se stanti, che possono essere richiamate in qualunque occasione e quindi vanno scritte una sola volta. Un miglioramento particolarmente decisivo si può però ottenere tramite l’uso di un controllo dedicato da affiancare a quello microprogrammato: • ad esempio, molte microarchitetture prevedono che alcuni bit del registro delle microistruzioni possano specificare il registro corretto; Autore: Sandro Petrizzelli 26 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori • analogamente, altri bit del registro delle microistruzioni possono indicare all’ALU l’operazione da compiere. Chiaramente, queste e altre facilitazioni analoghe sono comunque sottoposte al controllo microprogrammato e possono essere attivate tramite un valore speciale in un campo apposito delle microistruzioni. C’è del resto uno svantaggio anche nell’uso del controllo dedicato: esso potrebbe aumentare la durata del ciclo di sviluppo della macchina, dato che, pur non richiedendo una programmazione particolarmente complessa, rende comunque necessario lo sviluppo di circuiti integrati e/o di schede per la messa a punto. R Riid du uzziioon nee d deell C CP PII Fino ad ora abbiamo dunque trattato alcune tecniche per ridurre i costi di una unità di controllo microprogrammato, aventi tutte lo scopo di ridurre la dimensione della memoria di controllo. Vogliamo invece adesso occuparci di tre tecniche per migliorare le prestazioni e, in particolare, per ridurre il CPI. Uso di microcodice specializzato I microprogrammatori validi sanno sempre quando è opportuno scrivere microistruzioni corte e quando invece scrivere microistruzioni più lunghe (che quindi includano più informazioni). Un tipico esempio consiste nell’usare microistruzioni aggiuntive e specializzate per le istruzioni che si presentano con maggior frequenza, in modo da ridurre il CPI. Per individuare le istruzioni candidate ad essere eseguite tramite microcodice specializzato, basta studiare le stime delle distribuzioni delle istruzioni (come quelle esaminate nei capitoli precedenti) ed individuare le istruzioni con frequenza più elevata. Aggiunta di controllo dedicato L’aggiunta di un controllo dedicato, affiancato a quello microprogrammato, può sia ridurre i costi sia migliorare le operazioni. Ci sono vari esempi in proposito: • alcune macchine recenti riducono il CPI usando un codice specializzato per i trasferimenti di dati tra processore e memoria e per l’effettuazione di somme di tipo registro-registro o registro-memoria; • altre macchine tendono invece ad ottimizzare l’interfaccia del processore con la memoria, in base alle seguenti considerazioni: in molte macchine, si fa in modo che il microcodice esegua in continuazione test e salti fin quando la memoria non risulta pronta; tuttavia, a causa dell’inevitabile ritardo tra l’istante in cui una condizione diviene vera e l’istante in cui la microistruzione successiva viene letta, questa tecnica può comportare l’aggiunta di un ciclo di clock per ogni accesso alla memoria; molte macchine aggirano allora il problema facendo andare in stallo la microistruzione che tenta di accedere al registro MBR prima che l’operazione sia stata completata ( 15); nell’istante in cui il riferimento a memoria è stato 15 In generale, si verifica uno stallo quando una istruzione deve arrestarsi per uno o più cicli di clock in attesa della disponibilità di una qualche risorsa. aggiornamento: 10 luglio 2001 27 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 completato, la microistruzione che richiede il dato può essere portata a termine, evitando il ritardo di clock aggiuntivo per accedere alla memoria di controllo. Uso del “parallelismo” In alcuni casi, si può ridurre il CPI eseguendo più operazioni contemporanee tramite una sola microistruzione, nel qual caso si parla appunto di parallelismo. Questa tecnica solitamente richiede microistruzioni più lunghe e può portare dei benefici. Nonostante questo, non sempre tali benefici si verificano: un noto esempio di guadagno potenziale non realizzato fu un processore molto simile all’ Intel 8086, caratterizzato però dall’avere un bus supplementare all’interno dell’unità di controllo; la presenza di tale bus richiedeva 6 bit in più nelle microistruzioni, ma rendeva possibile ridurre la fase di esecuzione, da 3 a 2 cicli di clock, in molte macroistruzioni comuni del processore 8086; tuttavia, le macroistruzioni più comuni erano raggruppate in macroistruzioni che invece non potevano trarre alcun vantaggio da questa ottimizzazione, per cui venivano comunque eseguite tutte a velocità più bassa. Questione degli accessi incompleti alla memoria Abbiamo accennato, nei precedenti paragrafi, alla questione degli accessi incompleti alla memoria: quando la CPU richiede il prelievo di un dato o di una istruzione dalla memoria, l’accesso alla memoria si dirà incompleto fino a quando tale dato o tale istruzione non sarà stata interamente trasferita all’interno della CPU, dopodiché esso sarà completo. Analogo discorso, ovviamente, quando c’è da scrivere un dato in memoria (mentre invece le istruzioni possono solo essere prelevate). Si usa spesso la seguente terminologia: • tempo di accesso a memoria è il tempo che intercorre dal momento in cui il processore, tramite il MAR, invia l’indirizzo sul bus degli indirizzi fino al momento in cui si ottiene il dato richiesto, in forma stabile, nell’MDR (per i dati) o nell’ IR (per le istruzioni); • si definisce invece ciclo di memoria il tempo necessario per poter effettuare due operazioni consecutive di accesso alla memoria. In pratica, quindi, una volta completato un accesso a memoria, deve passare un certo tempo prima di poter attivare un eventuale successivo accesso alla memoria: tale tempo è la differenza tra il ciclo di memoria ed il tempo di accesso alla memoria. Tempo di accesso alla memoria Ciclo di memoria istante di accesso alla memoria Autore: Sandro Petrizzelli istante di accesso alla memoria 28 tempo aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Osserviamo inoltre che, fissato l’istante in cui la CPU pone sul bus l’indirizzo del dato richiesto, è necessario comunque attendere leggermente prima di attivare la lettura o la scrittura della memoria. Spesso, vengono usate coppie di stadi invertenti in cascata al fine di ritardare l’esecuzione di talune operazioni, per garantire che esse vengano eseguite solo dopo altre operazioni ad esse “propedeutiche”. A Arrcch hiitteettttu urraa aad d aaccccu um mu ullaatto orree Descrizione dell’architettura Mentre il DLX precedentemente introdotto è una architettura di tipo load/store, vogliamo ora parlare brevemente di una architettura ad accumulatore. Si tratta di un qualcosa che tutto sommato “somiglia” al DLX, con la differenza, ovviamente, che manca del tutto il banco di registri ed è sostituito dall’unico registro accumulatore. L’architettura è la seguente: Data Bus Acc MDR fetch IR MAP 1 ROM di microprogramma Tabella di mapping uPC FW PC Incrementatore del registro PC + MAR Address Bus Semplice architettura ad accumulatore. Da notare che, per semplicità, non sono stati riportati i latch in ingresso all’ALU. Non è stato nemmeno indicato un registro denominato X, che introdurremo nel prossimo paragrafo e che può essere considerato come una “replica” dell’accumulatore, ossia un registro con le stesse connessioni dell’accumulatore ma con funzioni ben diverse La descrizione di questa architettura appare abbastanza superflua, proprio perché rispecchia abbastanza quanto già detto nei precedenti paragrafi per il DLX, con le dovute differenze. Ad ogni modo, la giustificazione di alcune tra le connessioni qui riportate sarà fornita nel paragrafo seguente, in cui si discuterà l’implementazione delle varie modalità di indirizzamento. aggiornamento: 10 luglio 2001 29 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Modalità di indirizzamento e loro implementazione Nel capitolo 3 abbiamo già avuto modo di illustrare le principali modalità di indirizzamento usati dalle istruzioni in linguaggio macchina. Tuttavia, l’analisi fatta è stata del tutto generale, in quanto non ha tenuto conto affatto della particolare struttura hardware del calcolatore su cui realizzare l’indirizzamento. Vogliamo allora approfondire questo aspetto, basandoci su una semplice architettura ad accumulatore del tipo presentato nel paragrafo precedente. Per i nostri esempi, consideriamo una istruzione di caricamento (load) di un dato dalla memoria. Supponiamo, ad esempio, di voler specificare l’operando tramite l’indirizzamento diretto (o assoluto): l’istruzione simbolica è allora del tipo LDA A Per descrivere dettagliatamente quello che avviene a livello dell’hardware, possiamo usare il “solito” linguaggio di descrizione dell’hardware: allora, l’istruzione LDA A equivarrebbe banalmente a M[A] → Acc Il contenuto della locazione di memoria il cui indirizzo è A viene collocato nell’accumulatore. In realtà, l’architettura che stiamo considerando è più complessa, per cui anche il numero di operazioni elementari compiute (il cosiddetto microprogramma) è maggiore. Si ha quanto segue: PC+L (IR) → PC IR OP → MAR M[MAR] → MDR MDR → ACC 0 → µPC Prima di tutto, viene incrementato il registro PC, in modo da puntare all’istruzione successiva (supponendo che le istruzioni siano memorizzate sequenzialmente nella memoria). Successivamente, l’operando dell’istruzione attuale, contenuto nel registro IR, viene caricato nel registro MAR, il quale viene usato per indirizzare la memoria. La locazione di memoria indirizzata dal MAR viene dunque caricata nell’MDR, che a sua volta viene “copiato” nell’accumulatore. L’ultima istruzione presuppone che l’indirizzo corrispondente alla microistruzione che comanda il fetch, nella ROM di microprogramma, sia l’indirizzo 0 e quindi provvede a caricare tale indirizzo nel registro µPC. A quest’ultimo proposito, osserviamo che la fase di fetch di una istruzione (che è uguale per tutte le istruzioni) si ottiene nel modo seguente (microprogramma di fetch): PC → MAR M[MAR] → MDR MDR → IR MAP(IR CO ) → µPC Il registro MAR viene caricato con l’indirizzo contenuto nel registro PC e viene usato per indirizzare la memoria; la locazione di memoria indirizzata dal MAR viene dunque caricata nell’MDR, che a sua volta viene “copiato” nel registro IR; l’ultima istruzione individua, nella tabella di mapping, l’indirizzo corrispondente al codice Autore: Sandro Petrizzelli 30 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori operativo dell’istruzione appena caricata nel registro IR e provvede a caricare tale indirizzo nel registro µPC. Adesso consideriamo una istruzione di caricamento di un operando immediato: LDA #A Questa istruzione ha l’effetto di portare il valore A nell’accumulatore (A→Acc), tramite il seguente microprogramma: PC+L (IR) → PC IR OP → ACC 0 → µPC Adesso consideriamo il caso di indirizzamento indiretto: LDA (A) Il senso di tale istruzione è M[M[A]]→Acc. Il corrispondente microprogramma è allora il seguente: PC+L (IR) → PC IR OP → MAR M[MAR] → MDR MDR → MAR M[MAR] → MDR MDR → ACC 0 → µPC Questa sequenza di istruzioni spiega il motivo per cui, nell’architettura di esempio che stiamo considerando, esiste un collegamento diretto tra MAR e MDR: senza di esso, il programmatore non potrebbe utilizzare la modalità di indirizzamento indiretto. Questo collegamento sussiste anche nel DLX, dove però viene effettuato passando tramite la ALU: a livello concreto, il “passaggio” nel MAR del valore contenuto in MDR avviene tramite una operazione fittizia della ALU, realizzata ad esempio tramite due invertitori posti in cascata ( 16). Adesso consideriamo il cosiddetto indirizzamento indicizzato: LDA A,X Il senso di tale istruzione è M[X+A]→Acc, dove X è un registro “parallelo” all’accumulatore e che si suppone abbia le stesse sue connessioni. Il corrispondente microprogramma è il seguente: PC + L (IR) → PC X + IR OP → MAR M[MAR] → MDR MDR → ACC 0 → µPC 16 E’ ovvio che due stadi invertitori posti in cascata costituiscono un unico stadio detto buffer, che cioè riceve un ingresso e lo presenta invariato in uscita dopo un certo tempo (quello necessario affinché i segnali si propaghino attraverso i due stadi). aggiornamento: 10 luglio 2001 31 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Questa sequenza tiene ovviamente conto del fatto che A è un indirizzo di memoria situato nel campo operandi dell’istruzione (significa che A=IR OP nella notazione utilizzata). Notiamo inoltre che il risultato prodotto dalla ALU con la seconda operazione elementare (X + IR OP → MAR) viene portato direttamente nel registro MAR. Questo fatto serve sia a giustificare l’esistenza di un collegamento diretto tra uscita dell’ALU e registro MAR sia anche ad evidenziare quanto sia importante, in qualsiasi architettura, l’instradamento dei dati: generalmente, l’implementazione dell’instradamento risulta più costosa dei singoli componenti. Una variante del caso precedente è il cosiddetto indirizzamento indicizzato indiretto: LDA (A,X) Il senso di tale istruzione microprogramma è il seguente: è M[M[X+A]]→Acc, per cui il corrispondente PC + L (IR) → PC X + IR OP → MAR M[MAR] → MDR MDR → MAR M[MAR] → MDR MDR → ACC 0 → µPC Una ulteriore variante è il cosiddetto indirizzamento indiretto indicizzato: LDA (A),X Il senso di tale istruzione microprogramma è il seguente: è M[M[A]+X]→Acc, per cui il corrispondente PC + L (IR) → PC IR OP → MAR M[MAR] → MDR X+MDR → MAR M[MAR] → MDR MDR → ACC 0 → µPC Questa nuova sequenza serve a giustificare il perché anche il contenuto del registro MDR possa essere portato direttamente ad uno degli ingressi della ALU. Consideriamo ora l’indirizzamento indicizzato con post-incremento: LDA A,X+ Il senso di tale istruzione corrisponde a due operazioni successive: prima si effettua M[A+X]→Acc come nell’indirizzamento indicizzato, dopodiché si effettua l’incremento del registro X di una quantità d prefissata (implicita nel codice operativo e posseduta, come costante, dai circuiti costituenti la ALU). Il corrispondente microprogramma è il seguente: PC + L (IR) → PC Autore: Sandro Petrizzelli 32 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori X + IR OP → MAR M[MAR] → MDR MDR → ACC X + d → X 0 → µPC La situazione duale della precedenza è l’indirizzamento indicizzato con predecremento: LDA A,-X Il senso di tale istruzione corrisponde ancora a due operazioni successive: prima si effettua X-d→X e poi M[A+X]→Acc. Il corrispondente microprogramma è il seguente: PC + L (IR) → PC X – d → X X + IR OP → MAR M[MAR] → MDR MDR → ACC 0 → µPC Infine, esaminiamo una istruzione di memorizzazione di un dato in memoria, usando ad esempio l’indirizzamento diretto: STA A Il senso di questa istruzione è ovviamente ACC→M[A], per cui il corrispondente microprogramma è fatto nel modo seguente: PC+L (IR) → PC IR OP → MAR ACC → MDR MDR → M[MAR] 0 → µPC Istruzioni di salto Proseguendo sulla falsa riga del paragrafo precedente, vogliamo ora esaminare i microprogrammi relativi alle istruzioni di salto, sempre con riferimento all’architettura ad accumulatore (che, per la verità, nelle istruzioni di salto non influisce minimamente, dato che l’accumulatore non viene coinvolto). Come prima istruzione interessante da esaminare, consideriamo quella di salto incondizionato (jump), che forza il contenuto del registro PC ad un valore stabilito: ad esempio, l’istruzione JMP α equivale semplicemente ad α→PC. Il corrispondente microprogramma è allora fatto nel modo seguente: aggiornamento: 10 luglio 2001 33 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 PC+L (IR) → PC IR OP → PC 0 → µPC Questo microprogramma giustifica la linea di collegamento tra il registro IR ed il registro PC: serve a trasferire direttamente in quest’ultimo l’indirizzo di salto. Tra l’altro, si noti che la prima microistruzione risulta inutile, in quanto il PC verrà aggiornato con l’indirizzo di salto nella microistruzione successiva. Notiamo inoltre che il salto appena considerato non solo è incondizionato, ma anche senza rientro. Al contrario, quando il salto viene fatto perché corrisponde ad una chiamata di procedura (istruzione JSR, Jump SubRoutine), il rientro sarà necessario (al termine della procedura stessa). Per realizzare un meccanismo di questo tipo, bastano due accorgimenti: • in primo luogo, prima ancora di effettuare il salto, bisogna salvare l’indirizzo di rientro del salto, che sarà l’indirizzo dell’istruzione successiva a quella di salto. Tale indirizzo andrà posto in uno stack; • in secondo luogo, tutte le procedure devono avere, come istruzione finale, una istruzione di tipo RFS (Return From Subroutine), che ha appunto lo scopo di forzare il contenuto del registro PC al valore di rientro contenuto nello stack. In pratica, l’istruzione RFS è un salto incondizionato del tipo visto prima, con la differenza che l’indirizzo di salto deve essere preso dallo stack e non è quindi contenuto nel “corpo” dell’istruzione. Viene ora spontanea una domanda: in una architettura ad accumulatore, dove si trova lo stack? L’unica possibilità è quella di averlo in memoria: bisogna cioè gestire un’area di memoria come se fosse uno stack, usando due puntatori (uno per il fondo dello stack e l’altro per la cima, quest’ultimo chiamato stack pointer) e le operazioni di push (inserimento di un elemento nello stack) e di pop (estrazione di un elemento dallo stack). Sulla base di queste considerazioni, il microprogramma equivalente ad una istruzione del tipo JSR α è fatto nel modo seguente: PC+L (IR) → PC PC → M[SP] SP + L PC → SP α → PC 0 → µPC La prima istruzione serve a calcolare l’indirizzo della istruzione sequenzialmente successiva a quella di salto, ossia sostanzialmente l’indirizzo di rientro dalla subroutine; tale indirizzo viene inserito (push) nello stack e precisamente nella locazione di memoria puntata da un apposito registro SP (stack pointer) ( 17): in effetti, però, abbiamo un po’ semplificato, in quanto sappiamo che l’accesso al bus indirizzi può avvenire solo tramite il registro MAR; di conseguenza, la microistruzione PC → M[SP] equivale in realtà a tre microistruzioni: 17 Nel caso in cui l’architettura del calcolatore non preveda un registro SP, è possibile usare un qualsiasi registro che ne faccia le veci, purché ovviamente la sua lunghezza sia non inferiore alla lunghezza degli indirizzi di memoria. Autore: Sandro Petrizzelli 34 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori PC → MDR SP → MAR MDR → M[MAR] Una volta inserito nello stack l’indirizzo di rientro, il registro SP viene chiaramente aggiornato (SP + L PC → SP) in modo da tener conto di questo inserimento e puntare nuovamente alla cima dello stack. Solo a questo punto è possibile eseguire materialmente il salto, ossia inserire nel registro PC l’indirizzo (α) della prima istruzione della subroutine chiamata. L’ultima microistruzione abilita infine il fetch dell’istruzione puntata dal registro PC. Come si è detto prima, l’ultima istruzione della subroutine chiamata dovrà essere una RTS (solo codice operativo, niente operandi), che equivarrà al seguente microprogramma: PC+L (IR) → PC SP - L PC → SP SP → MAR M[MAR] → MDR MDR → PC 0 → µPC Ancora una volta, la prima istruzione è inutile, ma valgono le considerazioni già fatte in precedenza. L’istruzione successiva fa in modo che il registro SP non punti più alla cima dello stack (cioè alla prima locazione libera), ma all’elemento in cima allo stack, che corrisponde senz’altro all’indirizzo di rientro dalla subroutine: tale indirizzo, secondo le “modalità classiche” di accesso alla memoria, viene trasferito nell’MDR e da questo nel registro PC. C nee uzziioon ntteerrrru daa iin paarrttiiccoollaarree:: ssaallttoo d Caassoo p Le interruzioni (interrupt) saranno ampiamente descritte nella sezione successiva di questo capitolo. Per il momento, ci limitiamo a farne una analisi semplice, al fine di ricollegarci ai discorsi degli ultimi paragrafi e vedere, a livello di microprogrammazione, come vengono gestiti i passaggi ai programmi che si occupano della gestione di tali interruzioni. Le interruzioni sono sostanzialmente dei cambiamenti nel flusso di controllo durante l'esecuzione del programma: essi possono dipendere sia dall'esecuzione del programma sia da eventi “esterni” al programma, come ad esempio dei malfunzionamenti oppure dei “segnali” inviati dalle periferiche di Input ed Output. Il verificarsi di un interrupt ferma il programma in corso e trasferisce il controllo ad un cosiddetto gestore di interrupt: si tratta semplicemente di una procedura che esegue le azioni appropriate per l’interrupt e, quando ha finito, restituisce il controllo al programma interrotto. In particolare, il programma deve riprendere dall'esatto punto in cui era stato sospeso, il che significa che è necessario ripristinare tutti i registri interni allo stato di pre-interruzione. Quest’ultimo è un concetto chiave per la gestione degli interrupt ed è proprio quello che vogliamo discutere adesso. Supponiamo che un dato “evento” coinvolga una data periferica di I/O e questa necessiti dell’attenzione da parte della CPU; allora, deve inviare un interrupt alla CPU, utilizzando una apposita linea a livello hardware (linea INT): oltre ad asserire il segnale su tale linea, la periferica invia anche un particolare codice (detto aggiornamento: 10 luglio 2001 35 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 vettore di interrupt) che consente sostanzialmente alla CPU di individuare la subroutine opportuna tramite cui servire l’interruzione. La struttura generale di questa subroutine sarà del tipo seguente: SUB xxx: INT DISABLE SAVE REGISTERS ……… ……… ……… RTI Le prime due istruzioni sono importanti: la prima disabilita la linea degli interrupt, in modo che l’esecuzione della subroutine non possa essere interrotta da un altro interrupt ( 18); la seconda provvede al salvataggio di tutti i registri interni della CPU (in particolare il registro che “contiene” lo stato della macchina, chiamato PSW, Program Status Word), in modo che, quando la subroutine sarà terminata, si potrà ripristinare il normale flusso del programma a partire dall’esatto stato in cui era stato sospeso. Le successive istruzioni sono quelle specifiche per l’interruzione che si è verificata e quindi non le approfondiamo. L’ultima istruzione è RTI (Return From Interrupt): è una istruzione di salto incondizionato, che serve appunto a riprendere l’esecuzione del programma dal punto in cui era stato interrotto. Il microprogramma corrispondente all’istruzione RTI è del tipo seguente: PC+L (IR) → PC SP - L PSW → SP M[SP] → PSW SP-L PC → SP M[SP] → PC 0 → µPC Notiamo subito che le due microistruzioni M[SP]→PSW e M[SP]→PC sono in realtà rappresentative ciascuna delle solite tre microistruzioni che coinvolgono i registri MAR e MDR per l’accesso alla memoria, ma abbiamo preferito sintetizzare per non complicare troppo il microprogramma. Esaminando i dettagli di tale microprogramma, osserviamo che la prima istruzione (PC+L (IR) →PC) è ancora una volta inutile ma comunque inevitabile. Le successive due istruzioni presuppongono che il registro PSW della macchina sia stato salvato in cima allo stack prima di servire l’interruzione, per cui viene ricaricato, in modo da ripristinare lo stato della macchina al valore che aveva prima dell’interruzione. Le successive due istruzioni ripetono la stessa operazione con il registro PC, in modo da esso venga ripristinato con l’indirizzo dell’istruzione successiva a quella che ha comportato l’interruzione (ma potrebbe anche trattarsi, in alcuni casi, della stessa istruzione che ha causato l’interruzione e che deve essere rieseguiti). 18 E’ in effetti una semplificazione, ma, se non vengono presi gli opportuni accorgimenti (ad esempio dei meccanismi di priorità di cui parleremo in seguito), eventuali successive richieste di interrupt porterebbero la macchina in un loop pauroso! Autore: Sandro Petrizzelli 36 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori L Lee iin ntteerrrru uzziio on nii Introduzione Abbiamo detto che il controllo è la parte più difficile del progetto di un processore; in particolare, ciò che crea le maggiori difficoltà nel controllo sono le cosiddetti interruzioni: si tratta di eventi distinti dalle diramazioni, ma che, come esse, alterano il normale flusso di esecuzione delle istruzioni. Inizialmente, le interruzioni furono inventate per segnalare errori aritmetici (ad esempio l’overflow) oppure il verificarsi di eventi in tempo reale. Con il passare del tempo, però, le interruzioni hanno risolto una grande quantità di compiti difficili. Segue un elenco di alcuni tipici casi in cui si manifestano le interruzioni: • richiesta di I/O da parte di una periferica (caso tipico); • richiesta di un servizio del sistema operativo da parte di un programma utente; • attività di trace delle istruzioni (si tratta cioè di analizzare passo dopo passo come vengono eseguite le istruzioni); • inserzioni di punti di controllo (si tratta di interruzioni espressamente previste dal programmatore, al fine di ottenere un ausilio per il debug dei propri programmi); • overflow e underflow aritmetico; • mancanza di una pagina in memoria; • accesso disallineato alla memoria; • infrazione alle protezioni della memoria (dato che esistono parti della memoria “protette”, riservate ad esempio dal sistema operativo per l’I/O e quindi non accessibili da altri programmi); • uso di una istruzione indefinita (in questi casi, si può servire l’interruzione o far in modo che venga eseguita una NOP, cioè Not Operation, senza provocare interruzioni) ( 19); • malfunzionamenti dell’hardware; • caduta della tensione di alimentazione (in presenza di un simile evento, nel caso se ne abbia il tempo ed il processore sia sufficientemente veloce, possiamo provare a salvare il possibile sull’hard disk prima che la macchina cessi di funzionare). 19 Una istruzione non definita corrisponde ad una istruzione il cui codice operativo risulti sconosciuto al processore: una simile situazione si verifica a causa di errori nel compilatore oppure di guasti nella cache istruzioni o nel bus presente tra tale cache ed il registro IR; si verificano altre in alcune CPU appartenenti a “famiglie” in cui le singole realizzazione possono essere dotate, ad esempio, di unità aritmetiche sofisticate oppure non disporne e dover eseguire le corrispondenti operazioni mediante routine software. aggiornamento: 10 luglio 2001 37 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 T Teerrm miin noollooggiiaa d deellllee iin ntteerrrru uzziioon nii Col passare del tempo, i vari produttori di calcolatori hanno definito in modi sempre diversi i vari eventi di interruzione, determinando spesso confusione. Ad esempio, consideriamo una richiesta di I/O da parte di un dispositivo, ossia un caso tipico di interruzione inviata alla CPU: nell’ IBM 360, tale evento viene definito interruzione di ingresso/uscita, nel VAX viene definito interruzione da una periferica, nel Motorola 680x0 viene definito eccezione e nell’Intel 80x86 viene definito interruzione vettorizzata. In generale, tramite questo e tanti altri esempi, si nota che Intel e IBM usano tuttora il termine interruzioni (interrupt), mentre invece Motorola parla di eccezioni (exception); DEC, poi, usa vari termini, a seconda del tipo di evento: eccezioni, guasti (fault), aborti (abort), trappole (trap) o interruzioni. Concetti generali sulle interruzioni Come già anticipato, gli interrupt sono cambiamenti nel flusso di controllo durante l'esecuzione del programma: essi possono dipendere dall'esecuzione del programma ma anche da qualcos'altro; ad esempio, un programma può “istruire” il disco (tramite il suo controllore) per avviare il trasferimento di informazioni in memoria, indicando al disco stesso di fornire un interrupt non appena il trasferimento sarà finito. Il verificarsi di un interrupt ferma il programma in corso e trasferisce il controllo ad un cosiddetto gestore di interrupt: si tratta semplicemente di una procedura che esegue le azioni appropriate per l’interrupt e, quando ha finito, restituisce il controllo al programma interrotto. In particolare, il programma deve riprendere dall'esatto punto in cui era stato sospeso, il che significa che è necessario ripristinare tutti i registri interni allo stato di pre-interruzione. Quest’ultimo è un concetto chiave per la gestione degli interrupt, per cui merita di essere approfondito. I moderni calcolatori sono sotto il controllo di un insieme gerarchico di programmi, alla cui sommità c'è il sistema operativo. Per poter trasferire il controllo del calcolatore da un certo programma ad un altro (a seconda della operazione che si intende fare), è necessario fornire un meccanismo mediante il quale il funzionamento di un programma possa essere temporaneamente "interrotto" a favore di un altro: • una interruzione, come si detto poco fa, è un evento oppure un preciso segnale esterno, che provoca appunto la sospensione del funzionamento di un programma; • quando si verifica una interruzione, il contenuto di tutti i registri viene immediatamente salvato e il controllo del calcolatore passa ad una routine di servizio di interruzione, facente parte, in genere, del sistema operativo: essa determina da dove viene la richiesta di interruzione (generalmente esaminando un certo numero di flag o indicatori) e quindi trasferisce a sua volta il controllo ad una routine per la gestione di quel particolare tipo di interruzione; • successivamente, il funzionamento del programma viene ripreso esattamente dal punto in cui era stato sospeso: questo lo si ottiene facilmente inserendo in tutti i registri i valori che erano stati salvati al momento della interruzione. Autore: Sandro Petrizzelli 38 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Ogni interrupt specifico è identificato da un numero che ne indica il tipo: tale numero prende il nome di vettore di interrupt (o anche semplicemente codice interruzione). Per ogni interrupt, come si è detto, è previsto un programma gestore di interrupt, che esegue l'intero lavoro richiesto dall'interrupt. L'indirizzo del gestore di interrupt è contenuto a sua volta nella cosiddetta tabella degli interrupt, che viene indicizzata dal vettore di interrupt. Classificazione degli eventi eventi che determinano interruzioni E’ possibile, oltre che opportuno, classificare nel modo seguente gli eventi che determinano le interruzioni: • eventi sincroni o eventi asincroni (rispetto al programma): si parla di evento sincrono quando esso si verifica nello stesso punto ogni volta che il programma viene eseguito con gli stessi dati e la medesima allocazione di memoria; si parla invece di evento asincrono quando la sua occorrenza dipende da numerosi fattori oltre al programma, ai suoi dati ed alla memoria allocata, per cui esso non necessariamente si presenta negli stessi punti. Ad esempio, sono eventi asincroni i malfunzionamenti hardware: in generale, comunque, tutti gli eventi asincroni sono causati da dispositivi esterni alla memoria ed al processore; • eventi richiesti dall’utente o imposizioni esterne: un evento è richiesto dall’utente quando egli lo sollecita direttamente, mentre invece si parla di imposizione esterna quando l’evento è determinato da altri fattori diversi dalla volontà del programmatore; • eventi mascherabili o non mascherabili dall’utente: si dice che un evento è mascherabile se può essere disabilitato da un programma utente, nel senso che l’esecuzione può proseguire senza servire l’interruzione (cioè ignorandola, temporaneamente o definitivamente); se invece l’evento deve necessariamente essere “gestito”, allora si parla di evento non mascherabile; • eventi interni ed esterni alle istruzioni: un evento è detto interno ad una istruzione se può impedire il completamento della sua esecuzione, mentre invece è detto esterno se, prima di servirlo, si può completare l’esecuzione dell’istruzione; • possibilità di ripresa dell’esecuzione o necessità di terminazione: quando una interruzione determina necessariamente la terminazione dell’intero programma in corso di esecuzione, si parla di evento terminale (o anche non ripristinabile); se invece l’evento, pur imponendo la sospensione dell’esecuzione del programma, consente comunque la ripresa di tale esecuzione, allora si parla di evento ripristinabile. Al fine di chiarire ulteriormente quanto appena detto, la seguente tabella riporta alcuni eventi tipici e la loro classificazione dal punto di vista degli aspetti classificati poco fa. In particolare, gli esempi riportati fanno tutti riferimento a chiamate al sistema operativo ( 20): 20 Si tenga presente che, quando “lanciamo” l’esecuzione di un programma, quest’ultimo “gira” sotto il sistema operativo, ossia come sottoprogramma del sistema operativo. aggiornamento: 10 luglio 2001 39 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Evento Sincrono o asincrono Richiesta da utente o automatico Mascherabile o non mascherabile dall’utente Interno o esterno alle istruzioni Ripristinabile o non ripristinabile Asincrono Automatico Non mascherabile Esterno Ripristinabile Sincrono Richiesta da utente Mascherabile Esterno Ripristinabile Sincrono Richiesta da Utente Mascherabile Esterno Ripristinabile Sincrono Automatico Mascherabile Interno Non ripristinabile Sincrono Automatico Mascherabile Interno Ripristinabile Sincrono Automatico Non mascherabile Interno Ripristinabile Sincrono Automatico Mascherabile Interno Non ripristinabile Sincrono Automatico Non mascherabile Interno Non ripristinabile Sincrono Automatico Non mascherabile Interno Non ripristinabile Asincrono Automatico Non mascherabile Interno Non ripristinabile Asincrono Automatico Non mascherabile Interno Non ripristinabile Sincrono Richiesta da utente Non mascherabile Esterno Ripristinabile Richiesta di una periferica di I/O Esecuzione passopasso delle istruzioni Punto di controllo (break point) Overflow in aritmetica binaria Overflow o underflow in virgola mobile Mancanza di pagina Accesso disallineato alla memoria Violazione dei diritti di accesso alla memoria Uso di istruzioni non definite Guasto fisico Caduta di tensione Altre chiamate al sistema operativo E’ intuitivo comprendere che le situazioni più difficili sono quelle in cui le interruzioni si verificano “all’interno delle istruzioni”, ossia mentre è in corso una delle fasi di esecuzione delle singole istruzioni: come si è già detto e come approfondiremo anche in seguito, in alcuni casi (quelli più complessi) l’interruzione deve essere immediatamente servita, mentre invece in altri si può comunque aspettare che l’esecuzione dell’istruzione venga completata; Nella tabella spicca il fatto che la maggior parte delle interruzioni sono di tipo sincrono e sono in qualche modo relative al software, ossia legate all’esecuzione delle istruzioni ed agli eventi che tale esecuzione determina. La maggior parte delle interruzioni asincrone sono invece legate all’hardware, come i guasti fisici o le cadute di tensione. Si nota anche che tutte le interruzioni avvengono non per volontà del programmatore, tranne quelle da egli espressamente previste. Si evidenzia invece un sostanziale bilancio tra le interruzioni mascherabili e quelle non mascherabili, così come tra quelle ripristinabili e quelle non ripristinabili. Una cosa interessante da notare è il fatto che l’interruzione dovuta ad overflow, sia in aritmetica intera sia in virgola mobile, è ritenuta mascherabile: questo perché se si decidesse di realizzare un divisore che, in presenza di denominatore nullo, deve fornire il massimo valore rappresentabile in una word, potremmo scegliere di proseguire senza arrestare il funzionamento. Bisogna infatti tener conto che l’overflow non è altro che la presenza di un riporto sul bit del segno: nel caso dell’aritmetica binaria, potrebbe essere che si stanno usando numeri non segnati, per cui tale riporto non crea alcun problema; nel caso di virgola mobile, invece, potrebbe essere sufficiente far stampare un messaggio a video e proseguire ugualmente ( 21). Altra cosa interessante è invece quella per cui una interruzione dovuta ad un accesso disallineato a memoria è ritenuta non ripristinabile: in generale, in 21 A tal proposito, sottolineiamo che l’eventuale stampa del messaggio di errore avviene a cura della routine di gestione dell’interrupt. Autore: Sandro Petrizzelli 40 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori presenza di un accesso disallineato a memoria, dobbiamo saltare ad una subroutine di disallineamento, la quale deve leggere le due parole di memoria (consecutive) contenenti la parola disallineata di interesse, spostarle nella CPU e infine comporle per ottenere la parola di interesse. Ricordiamo che gli accessi disallineati a memoria si possono avere solo su macchine che usano indirizzamento della memoria superiore al byte (ad esempio, indirizzamento alle parole): in questo tipo di macchine, è sempre opportuno prevedere le interruzioni dovute al disallineamento. E’ bene inoltre sottolineare che un accesso disallineato a memoria riguarda sempre l’accesso ai dati contenuti nella memoria e non l’accesso alle istruzioni, visto che il registro PC viene regolarmente incrementato di 4 byte. Per concludere, segnaliamo che, per le applicazioni particolarmente delicate (pensiamo ai sistemi per transazioni economiche), esistono delle particolari CPU cosiddette fault-tolerant, ossia tolleranti al guasto: si tratta di CPU multiprocessore, che ospitano più processori identici su uno stesso chip; quando un processore si accorge che un altro processore sta avendo dei problemi di difficile o impossibile risoluzione, è in grado di sostituirlo (processo killer). Rilevazione delle interruzioni da parte del controllo Abbiamo detto in precedenza che l’attività di controllo in un calcolatore è comodamente rappresentabile tramite un diagramma a stati finiti, in cui ogni stato corrisponde a tutte le operazioni che si possono compiere in uno stesso ciclo di clock ed in cui le transizioni di stato sono determinate dall’accadere di determinati eventi, non ultime le interruzioni. In particolare, abbiamo in precedenza considerato il seguente esempio di diagramma a stati finiti, in cui sono riportati gli stati e le relative operazioni relative alle prime due fasi di esecuzione di una istruzione, vale a dire prelevamento dalla memoria e decodifica (con preparazione degli operandi): MAR ç PC Accesso alla memoria incompleto IR ç M[MAR] Accesso alla memoria completo PC ç PC + 4 A ç Rs1 B ç Rs2 L’ultimo stato presenta evidentemente una diramazione, in quanto le successive operazioni da compiere dipendono dal tipo di istruzione (ad esempio una istruzione aritmetica di somma o di differenza oppure una istruzione di caricamento di un dato dalla memoria oppure di memorizzazione di un dato nella memoria). Quel diagramma deve essere necessariamente modificato se si vuole integrare il controllo con il riconoscimento e la gestione delle interruzioni. A tal fine, dobbiamo tener conto della possibilità che l’eventuale interruzione sia esterna o interna all’istruzione: nel primo caso, si può attendere il completamente dell’esecuzione aggiornamento: 10 luglio 2001 41 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 dell’istruzione prima di servire l’interruzione, mentre invece, in caso contrario, l’interruzione deve essere servita immediatamente e quindi l’esecuzione dell’istruzione deve essere sospesa e ripristinata successivamente. Consideriamo la fase di caricamento dell’istruzione nel registro IR, corrispondente al secondo stato nel diagramma prima riportato: • in assenza di qualunque interruzione, il ciclo di caricamento viene ripetuto (accesso alla memoria incompleto) fino a quando l’intera istruzione si trova in IR (accesso alla memoria completo); • in presenza di interruzione, invece, il comportamento del controllo dipende dal tipo di interruzione: • se l’interruzione è interna, come ad esempio la mancanza, in memoria principale, della pagina contenente l’istruzione da caricare o parte di essa ( 22), non si può fare a meno di servire l’interruzione prima di decodificare l’istruzione stessa; • se invece l’interruzione è esterna, si può procedere con l’esecuzione e fare in modo che l’interruzione risulti pendente, in modo da servirla prima di passare all’esecuzione dell’istruzione successiva. Il seguente diagramma di flusso descrive il meccanismo adottato: Ritorno da interruzione Interruzione pendente IAR ç PC MAR ç PC IR ç M[MAR] Mancanza di pagina PC ç indirizzo del gestore dell'interruzione; Accesso alla memoria incompleto (nessuna interruzione) Accesso alla memoria completo (nessuna interruzione) interruzione ripristinata PC ç PC + 4 A ç Rs1 B ç Rs2 Nel momento in cui si decide di servire l’interruzione, il valore del PC viene registrato nel registro IAR (indirizzo di ritorno dalle interruzioni) e, al ciclo di clock successivo, il PC viene caricato con l’indirizzo del gestore dell’interruzione e quindi tale gestore può essere eseguito. Fatto questo, l’interruzione può essere ripristinata, nel senso che il contenuto originale del PC (cioè quello prima della chiamata dell’istruzione) viene riportato appunto in PC e l’esecuzione dell’istruzione ripresa dal punto in cui era stata interrotta. Quella appena descritta non è però l’unica situazione possibile. Infatti, una interruzione potrebbe verificarsi anche nella terza fase di esecuzione dell’istruzione, ad esempio quando il risultato di una somma determina un overflow oppure quando, 22 Questo concetto sarà chiarito successivamente, quando si parlerà di memoria virtuale. In generale, diciamo che si tratta di quella situazione in cui i dati cui si è interessati non si trovano nella memoria principale ma su una memoria secondaria, per cui devono essere prima caricati nella memoria principale e poi da questa all’interno della CPU. Autore: Sandro Petrizzelli 42 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori per un caricamento di una dato dalla memoria, si verifica nuovamente una mancanza di pagina. In questo secondo caso, per esempio, il diagramma di prima si modifica nel modo seguente: Ritorno da interruzione Interruzione pendente IAR ç PC MAR ç PC Accesso alla memoria incompleto (nessuna interruzione) IR ç M[MAR] Mancanza di pagina PC ç indirizzo del gestore dell'interruzione; Accesso alla memoria completo (nessuna interruzione) interruzione ripristinata PC ç PC + 4 A ç Rs1 B ç Rs2 Mancanza di pagina Accesso alla memoria incompleto (nessuna interruzione) MDR ç M[MAR] Accesso alla memoria completo (nessuna interruzione) Interruzione in caso di mancanza di pagina per una operazione di caricamento dati in memoria Situazione del tutto analoga si ha quando l’istruzione in corso di esecuzione prevede una somma: Ritorno da interruzione Interruzione pendente IAR ç PC MAR ç PC Accesso alla memoria incompleto (nessuna interruzione) IR ç M[MAR] Mancanza di pagina PC ç indirizzo del gestore dell'interruzione; Accesso alla memoria completo (nessuna interruzione) interruzione ripristinata PC ç PC + 4 A ç Rs1 B ç Rs2 Overflow C ç A+Temp Nessun overflow Interruzione in caso di overflow per una operazione di somma aggiornamento: 10 luglio 2001 43 Autore: Sandro Petrizzelli Appunti di “Calcolatori Elettronici” – Capitolo 5 Da notare che le istruzioni che possono provocare una interruzione, come appunto quelle di somma, si occupano loro stesse della verifica di interruzione. Il controllo per il DLX Il seguente diagramma a stati finiti rappresenta il controllo dedicato per il DLX: Ritorno da interruzione MAR ç PC IR ç M[MAR] IAR ç PC Accesso alla memoria incompleto (nessuna interruzione) Accesso alla memoria completo (nessuna interruzione) PC ç 0 PC ç PC + 4 A ç Rs1 B ç Rs2 ripristino interruzione Trasferimento dati ALU Inizializzazione Salto Diramazione Dato che i particolari da evidenziare in diagrammi come questo sono davvero tanti, si ritiene poco opportuno disegnare la macchina a stati finiti DLX in un’unica figura contenenti tutti i 52 stati: di conseguenza, la figura appena riportata mostra solo il livello più alto del controllo, che contiene cinque stati ed i riferimenti agli stati rimanenti; questi ultimi sono dettagliatamente descritti nelle figure da 5.10 (pag. 187) a 5.14 (pag. 189) del testo. La figura appena riportata mostra dunque i primi due stati dell’esecuzione delle istruzioni: il prelievo dell’istruzione dalla memoria, all’indirizzo puntato da PC, e la decodifica dell’istruzione. Il primo stato viene ripetuto fin quando l’intera istruzione non viene prelevata dalla memoria o si verifica una interruzione. Nel caso in cui tale interruzione venga rilevata, il contenuto del PC viene salvato in IAR e poi PC viene aggiornato all’indirizzo della routine di servizio delle interruzioni: in particolare, la figura riporta un indirizzo puramente simbolico pari a 0, che è appunto da intendersi come quello della routine di servizio delle interruzioni ( 23). 23 L’indirizzo 0, pur essendo solo simbolico nella figura riportata, nasconde in realtà un altro significato: quando stiamo lavorando sul nostro calcolatore e, per un qualunque motivo, ne chiediamo il reset (ad esempio spingendo l’apposito pulsante sul pannello frontale), automaticamente il registro PC viene caricato con l’indirizzo 0, che individua la prima istruzione situata nella ROM del calcolatore: tale istruzione corrisponde al kernel del computer, che si occupa di compiere delle verifiche sulle periferiche installate nonché sulla quantità di memoria disponibile; in particolare, il “test” sulla memoria avviene tramite un ciclo di scrittura/lettura: vengono prima scritti tutti i byte a 0 e poi vengono letti controllando Autore: Sandro Petrizzelli 44 aggiornamento: 10 luglio 2001 Tecniche di base per la realizzazione dei processori Gli ultimi tre stadi dell’esecuzione delle istruzioni, vale a dire esecuzione e calcolo degli indirizzi, accesso alla memoria e scrittura del risultato, sono mostrati nelle figure da 5.10 (pag. 187) a 5.14 (pag. 189) del testo, di cui si consiglia la lettura. Autore: Sandro Petrizzelli e-mail: [email protected] sito personale: http://users.iol.it/sandry che siano tutti a 0; successivamente, tutti i byte vengono scritti a 1 e vengono poi letti controllando che sia tutti 1. L’eventuale rilevazione di anche un solo byte danneggiato comporta l’arresto della macchina, dato che nella memoria RAM non sono tollerate “interruzioni” di nessun tipo. Da notare che questo vincolo così rigido non esiste invece per il disco rigido, dove una eventuale parola risultata danneggiata viene semplicemente contrassegnata come tale e ritenuta non più disponibile, senza che però il disco intero vada riparato o, peggio, buttato. aggiornamento: 10 luglio 2001 45 Autore: Sandro Petrizzelli